// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app import ( "net/http" "runtime/debug" "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 Emoji einterfaces.EmojiInterface Ldap einterfaces.LdapInterface Metrics einterfaces.MetricsInterface Mfa einterfaces.MfaInterface Saml einterfaces.SamlInterface configFile string newStore func() store.Store sessionCache *utils.Cache } 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()?") } app := &App{ goroutineExitSignal: make(chan struct{}, 1), Srv: &Server{ Router: mux.NewRouter(), }, sessionCache: utils.NewLru(model.SESSION_CACHE_SIZE), configFile: "config.json", } for _, option := range options { option(app) } if utils.T == nil { utils.TranslationsPreInit() } utils.LoadGlobalConfig(app.configFile) utils.InitTranslations(utils.Cfg.LocalizationSettings) l4g.Info(utils.T("api.server.new_server.init.info")) app.initEnterprise() 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.initJobs() app.initBuiltInPlugins() app.Srv.Router.HandleFunc("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}", app.ServePluginRequest) app.Srv.Router.HandleFunc("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}/{anything:.*}", app.ServePluginRequest) 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 brandInterface func(*App) einterfaces.BrandInterface func RegisterBrandInterface(f func(*App) einterfaces.BrandInterface) { brandInterface = 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 elasticsearchInterface func(*App) einterfaces.ElasticsearchInterface func RegisterElasticsearchInterface(f func(*App) einterfaces.ElasticsearchInterface) { elasticsearchInterface = f } var emojiInterface func(*App) einterfaces.EmojiInterface func RegisterEmojiInterface(f func(*App) einterfaces.EmojiInterface) { emojiInterface = 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) } if brandInterface != nil { a.Brand = brandInterface(a) } if clusterInterface != nil { a.Cluster = clusterInterface(a) } if complianceInterface != nil { a.Compliance = complianceInterface(a) } if elasticsearchInterface != nil { a.Elasticsearch = elasticsearchInterface(a) } if emojiInterface != nil { a.Emoji = emojiInterface(a) } 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) } } func (a *App) initJobs() { a.Jobs = jobs.NewJobServer(a.Config, a.Srv.Store) 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)) { old := utils.Cfg.Clone() f(utils.Cfg) utils.InvokeGlobalConfigListeners(old, utils.Cfg) } func (a *App) PersistConfig() { utils.SaveConfig(a.ConfigFileName(), a.Config()) } func (a *App) ReloadConfig() { debug.FreeOSMemory() utils.LoadGlobalConfig(a.ConfigFileName()) // start/restart email batching job if necessary a.InitEmailBatching() } func (a *App) ConfigFileName() string { return utils.CfgFileName } // 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) }