// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app import ( "net/http" "sync/atomic" l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/mattermost-server/einterfaces" ejobs "github.com/mattermost/mattermost-server/einterfaces/jobs" "github.com/mattermost/mattermost-server/jobs" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/plugin/pluginenv" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/store/sqlstore" "github.com/mattermost/mattermost-server/utils" ) type App struct { goroutineCount int32 goroutineExitSignal chan struct{} Srv *Server PluginEnv *pluginenv.Environment PluginConfigListenerId string EmailBatching *EmailBatchingJob Hubs []*Hub HubsStopCheckingForDeadlock chan bool Jobs *jobs.JobServer AccountMigration einterfaces.AccountMigrationInterface Brand einterfaces.BrandInterface Cluster einterfaces.ClusterInterface Compliance einterfaces.ComplianceInterface DataRetention einterfaces.DataRetentionInterface Elasticsearch einterfaces.ElasticsearchInterface Ldap einterfaces.LdapInterface Metrics einterfaces.MetricsInterface Mfa einterfaces.MfaInterface Saml einterfaces.SamlInterface newStore func() store.Store } var appCount = 0 // New creates a new App. You must call Shutdown when you're done with it. // XXX: For now, only one at a time is allowed as some resources are still shared. func New(options ...Option) *App { appCount++ if appCount > 1 { panic("Only one App should exist at a time. Did you forget to call Shutdown()?") } l4g.Info(utils.T("api.server.new_server.init.info")) app := &App{ goroutineExitSignal: make(chan struct{}, 1), Jobs: &jobs.JobServer{}, Srv: &Server{ Router: mux.NewRouter(), }, } app.initEnterprise() for _, option := range options { option(app) } if app.newStore == nil { app.newStore = func() store.Store { return store.NewLayeredStore(sqlstore.NewSqlSupplier(app.Config().SqlSettings, app.Metrics), app.Metrics, app.Cluster) } } app.Srv.Store = app.newStore() app.Jobs.Store = app.Srv.Store app.Srv.Router.NotFoundHandler = http.HandlerFunc(app.Handle404) app.Srv.WebSocketRouter = &WebSocketRouter{ app: app, handlers: make(map[string]webSocketHandler), } return app } func (a *App) Shutdown() { appCount-- l4g.Info(utils.T("api.server.stop_server.stopping.info")) a.StopServer() a.HubStop() a.ShutDownPlugins() a.WaitForGoroutines() a.Srv.Store.Close() a.Srv = nil l4g.Info(utils.T("api.server.stop_server.stopped.info")) } var accountMigrationInterface func(*App) einterfaces.AccountMigrationInterface func RegisterAccountMigrationInterface(f func(*App) einterfaces.AccountMigrationInterface) { accountMigrationInterface = f } var clusterInterface func(*App) einterfaces.ClusterInterface func RegisterClusterInterface(f func(*App) einterfaces.ClusterInterface) { clusterInterface = f } var complianceInterface func(*App) einterfaces.ComplianceInterface func RegisterComplianceInterface(f func(*App) einterfaces.ComplianceInterface) { complianceInterface = f } var dataRetentionInterface func(*App) einterfaces.DataRetentionInterface func RegisterDataRetentionInterface(f func(*App) einterfaces.DataRetentionInterface) { dataRetentionInterface = f } var jobsDataRetentionJobInterface func(*App) ejobs.DataRetentionJobInterface func RegisterJobsDataRetentionJobInterface(f func(*App) ejobs.DataRetentionJobInterface) { jobsDataRetentionJobInterface = f } var jobsElasticsearchAggregatorInterface func(*App) ejobs.ElasticsearchAggregatorInterface func RegisterJobsElasticsearchAggregatorInterface(f func(*App) ejobs.ElasticsearchAggregatorInterface) { jobsElasticsearchAggregatorInterface = f } var jobsElasticsearchIndexerInterface func(*App) ejobs.ElasticsearchIndexerInterface func RegisterJobsElasticsearchIndexerInterface(f func(*App) ejobs.ElasticsearchIndexerInterface) { jobsElasticsearchIndexerInterface = f } var jobsLdapSyncInterface func(*App) ejobs.LdapSyncInterface func RegisterJobsLdapSyncInterface(f func(*App) ejobs.LdapSyncInterface) { jobsLdapSyncInterface = f } var ldapInterface func(*App) einterfaces.LdapInterface func RegisterLdapInterface(f func(*App) einterfaces.LdapInterface) { ldapInterface = f } var metricsInterface func(*App) einterfaces.MetricsInterface func RegisterMetricsInterface(f func(*App) einterfaces.MetricsInterface) { metricsInterface = f } var mfaInterface func(*App) einterfaces.MfaInterface func RegisterMfaInterface(f func(*App) einterfaces.MfaInterface) { mfaInterface = f } var samlInterface func(*App) einterfaces.SamlInterface func RegisterSamlInterface(f func(*App) einterfaces.SamlInterface) { samlInterface = f } func (a *App) initEnterprise() { if accountMigrationInterface != nil { a.AccountMigration = accountMigrationInterface(a) } a.Brand = einterfaces.GetBrandInterface() if clusterInterface != nil { a.Cluster = clusterInterface(a) } if complianceInterface != nil { a.Compliance = complianceInterface(a) } a.Elasticsearch = einterfaces.GetElasticsearchInterface() if ldapInterface != nil { a.Ldap = ldapInterface(a) utils.AddConfigListener(func(_, cfg *model.Config) { if err := utils.ValidateLdapFilter(cfg, a.Ldap); err != nil { panic(utils.T(err.Id)) } }) } if metricsInterface != nil { a.Metrics = metricsInterface(a) } if mfaInterface != nil { a.Mfa = mfaInterface(a) } if samlInterface != nil { a.Saml = samlInterface(a) utils.AddConfigListener(func(_, cfg *model.Config) { a.Saml.ConfigureSP() }) } if dataRetentionInterface != nil { a.DataRetention = dataRetentionInterface(a) } if jobsDataRetentionJobInterface != nil { a.Jobs.DataRetentionJob = jobsDataRetentionJobInterface(a) } if jobsElasticsearchAggregatorInterface != nil { a.Jobs.ElasticsearchAggregator = jobsElasticsearchAggregatorInterface(a) } if jobsElasticsearchIndexerInterface != nil { a.Jobs.ElasticsearchIndexer = jobsElasticsearchIndexerInterface(a) } if jobsLdapSyncInterface != nil { a.Jobs.LdapSync = jobsLdapSyncInterface(a) } } func (a *App) Config() *model.Config { return utils.Cfg } func (a *App) UpdateConfig(f func(*model.Config)) { f(utils.Cfg) } // Go creates a goroutine, but maintains a record of it to ensure that execution completes before // the app is destroyed. func (a *App) Go(f func()) { atomic.AddInt32(&a.goroutineCount, 1) go func() { f() atomic.AddInt32(&a.goroutineCount, -1) select { case a.goroutineExitSignal <- struct{}{}: default: } }() } // WaitForGoroutines blocks until all goroutines created by App.Go exit. func (a *App) WaitForGoroutines() { for atomic.LoadInt32(&a.goroutineCount) != 0 { <-a.goroutineExitSignal } } func (a *App) Handle404(w http.ResponseWriter, r *http.Request) { err := model.NewAppError("Handle404", "api.context.404.app_error", nil, "", http.StatusNotFound) err.Translate(utils.T) l4g.Debug("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r)) utils.RenderWebError(err, w, r) }