From 1f24d1afecfae8c01d247b397151fd6b743c20a8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 12 Aug 2020 12:28:20 +0100 Subject: [PATCH] Start HTTP endpoint refactoring --- cmd/dendrite-monolith-server/main.go | 33 +++++------ internal/config/config.go | 57 ++++++++++++++++--- internal/config/config_appservice.go | 11 ++-- internal/config/config_clientapi.go | 16 ++++-- internal/config/config_currentstate.go | 11 ++-- internal/config/config_eduserver.go | 11 ++-- internal/config/config_federationapi.go | 16 ++++-- internal/config/config_federationsender.go | 11 ++-- internal/config/config_keyserver.go | 11 ++-- internal/config/config_mediaapi.go | 16 ++++-- internal/config/config_roomserver.go | 11 ++-- internal/config/config_serverkey.go | 11 ++-- internal/config/config_syncapi.go | 11 ++-- internal/config/config_userapi.go | 11 ++-- internal/httputil/httpapi.go | 12 +++- internal/setup/base.go | 65 ++++++++++++++++------ internal/test/config.go | 48 ++++++++-------- internal/test/server.go | 6 +- 18 files changed, 222 insertions(+), 146 deletions(-) diff --git a/cmd/dendrite-monolith-server/main.go b/cmd/dendrite-monolith-server/main.go index 8f98cdd04..86396c4d7 100644 --- a/cmd/dendrite-monolith-server/main.go +++ b/cmd/dendrite-monolith-server/main.go @@ -53,18 +53,18 @@ func main() { // statements in the configuration so that we know where to find // the API endpoints. They'll listen on the same port as the monolith // itself. - addr := config.Address(*httpBindAddr) - cfg.AppServiceAPI.Listen = addr - cfg.ClientAPI.Listen = addr - cfg.CurrentStateServer.Listen = addr - cfg.EDUServer.Listen = addr - cfg.FederationAPI.Listen = addr - cfg.FederationSender.Listen = addr - cfg.KeyServer.Listen = addr - cfg.MediaAPI.Listen = addr - cfg.RoomServer.Listen = addr - cfg.ServerKeyAPI.Listen = addr - cfg.SyncAPI.Listen = addr + addr := config.HTTPAddress("http://" + *httpBindAddr) + cfg.AppServiceAPI.InternalAPI.Connect = addr + cfg.ClientAPI.InternalAPI.Connect = addr + cfg.CurrentStateServer.InternalAPI.Connect = addr + cfg.EDUServer.InternalAPI.Connect = addr + cfg.FederationAPI.InternalAPI.Connect = addr + cfg.FederationSender.InternalAPI.Connect = addr + cfg.KeyServer.InternalAPI.Connect = addr + cfg.MediaAPI.InternalAPI.Connect = addr + cfg.RoomServer.InternalAPI.Connect = addr + cfg.ServerKeyAPI.InternalAPI.Connect = addr + cfg.SyncAPI.InternalAPI.Connect = addr } base := setup.NewBaseDendrite(cfg, "Monolith", *enableHTTPAPIs) @@ -159,14 +159,7 @@ func main() { // Expose the matrix APIs directly rather than putting them under a /api path. go func() { - serv := http.Server{ - Addr: *httpBindAddr, - WriteTimeout: setup.HTTPServerTimeout, - Handler: base.BaseMux, - } - - logrus.Info("Listening on ", serv.Addr) - logrus.Fatal(serv.ListenAndServe()) + base.SetupAndServeHTTP(*httpBindAddr, *httpBindAddr) }() // Handle HTTPS if certificate and key are provided if *certFile != "" && *keyFile != "" { diff --git a/internal/config/config.go b/internal/config/config.go index 6cd04722e..bad39f236 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "io/ioutil" + "net/url" "path/filepath" "regexp" "strings" @@ -110,6 +111,15 @@ type Derived struct { // servers from creating RoomIDs in exclusive application service namespaces } +type InternalAPIOptions struct { + Listen HTTPAddress `yaml:"listen"` + Connect HTTPAddress `yaml:"connect"` +} + +type ExternalAPIOptions struct { + Listen HTTPAddress `yaml:"listen"` +} + // A Path on the filesystem. type Path string @@ -132,6 +142,17 @@ type Topic string // An Address to listen on. type Address string +// An HTTPAddress to listen on, starting with either http:// or https://. +type HTTPAddress string + +func (h HTTPAddress) GetAddress() (Address, error) { + url, err := url.Parse(string(h)) + if err != nil { + return "", err + } + return Address(url.Host), nil +} + // FileSizeBytes is a file size in bytes type FileSizeBytes int64 @@ -360,6 +381,26 @@ func checkPositive(configErrs *ConfigErrors, key string, value int64) { } } +// checkURL verifies that the parameter is a valid URL +func checkURL(configErrs *ConfigErrors, key, value string) { + if value == "" { + configErrs.Add(fmt.Sprintf("missing config key %q", key)) + return + } + url, err := url.Parse(value) + if err != nil { + configErrs.Add(fmt.Sprintf("config key %q contains invalid URL (%s)", key, err.Error())) + return + } + switch url.Scheme { + case "http": + case "https": + default: + configErrs.Add(fmt.Sprintf("config key %q URL should be http:// or https://", key)) + return + } +} + // checkLogging verifies the parameters logging.* are valid. func (config *Dendrite) checkLogging(configErrs *ConfigErrors) { for _, logrusHook := range config.Logging { @@ -450,7 +491,7 @@ func (config *Dendrite) AppServiceURL() string { // If we support HTTPS we need to think of a practical way to do certificate validation. // People setting up servers shouldn't need to get a certificate valid for the public // internet for an internal API. - return "http://" + string(config.AppServiceAPI.Listen) + return string(config.AppServiceAPI.InternalAPI.Connect) } // RoomServerURL returns an HTTP URL for where the roomserver is listening. @@ -459,7 +500,7 @@ func (config *Dendrite) RoomServerURL() string { // If we support HTTPS we need to think of a practical way to do certificate validation. // People setting up servers shouldn't need to get a certificate valid for the public // internet for an internal API. - return "http://" + string(config.RoomServer.Listen) + return string(config.RoomServer.InternalAPI.Connect) } // UserAPIURL returns an HTTP URL for where the userapi is listening. @@ -468,7 +509,7 @@ func (config *Dendrite) UserAPIURL() string { // If we support HTTPS we need to think of a practical way to do certificate validation. // People setting up servers shouldn't need to get a certificate valid for the public // internet for an internal API. - return "http://" + string(config.UserAPI.Listen) + return string(config.UserAPI.InternalAPI.Connect) } // CurrentStateAPIURL returns an HTTP URL for where the currentstateserver is listening. @@ -477,7 +518,7 @@ func (config *Dendrite) CurrentStateAPIURL() string { // If we support HTTPS we need to think of a practical way to do certificate validation. // People setting up servers shouldn't need to get a certificate valid for the public // internet for an internal API. - return "http://" + string(config.CurrentStateServer.Listen) + return string(config.CurrentStateServer.InternalAPI.Connect) } // EDUServerURL returns an HTTP URL for where the EDU server is listening. @@ -486,7 +527,7 @@ func (config *Dendrite) EDUServerURL() string { // If we support HTTPS we need to think of a practical way to do certificate validation. // People setting up servers shouldn't need to get a certificate valid for the public // internet for an internal API. - return "http://" + string(config.EDUServer.Listen) + return string(config.EDUServer.InternalAPI.Connect) } // FederationSenderURL returns an HTTP URL for where the federation sender is listening. @@ -495,7 +536,7 @@ func (config *Dendrite) FederationSenderURL() string { // If we support HTTPS we need to think of a practical way to do certificate validation. // People setting up servers shouldn't need to get a certificate valid for the public // internet for an internal API. - return "http://" + string(config.FederationSender.Listen) + return string(config.FederationSender.InternalAPI.Connect) } // ServerKeyAPIURL returns an HTTP URL for where the server key API is listening. @@ -504,7 +545,7 @@ func (config *Dendrite) ServerKeyAPIURL() string { // If we support HTTPS we need to think of a practical way to do certificate validation. // People setting up servers shouldn't need to get a certificate valid for the public // internet for an internal API. - return "http://" + string(config.ServerKeyAPI.Listen) + return string(config.ServerKeyAPI.InternalAPI.Connect) } // KeyServerURL returns an HTTP URL for where the key server is listening. @@ -513,7 +554,7 @@ func (config *Dendrite) KeyServerURL() string { // If we support HTTPS we need to think of a practical way to do certificate validation. // People setting up servers shouldn't need to get a certificate valid for the public // internet for an internal API. - return "http://" + string(config.KeyServer.Listen) + return string(config.KeyServer.InternalAPI.Connect) } // SetupTracing configures the opentracing using the supplied configuration. diff --git a/internal/config/config_appservice.go b/internal/config/config_appservice.go index b8962dedb..a042691db 100644 --- a/internal/config/config_appservice.go +++ b/internal/config/config_appservice.go @@ -29,8 +29,7 @@ type AppServiceAPI struct { Matrix *Global `yaml:"-"` Derived *Derived `yaml:"-"` // TODO: Nuke Derived from orbit - Listen Address `yaml:"listen"` - Bind Address `yaml:"bind"` + InternalAPI InternalAPIOptions `yaml:"internal_api"` Database DatabaseOptions `yaml:"database"` @@ -38,15 +37,15 @@ type AppServiceAPI struct { } func (c *AppServiceAPI) Defaults() { - c.Listen = "localhost:7777" - c.Bind = "localhost:7777" + c.InternalAPI.Listen = "http://localhost:7777" + c.InternalAPI.Connect = "http://localhost:7777" c.Database.Defaults() c.Database.ConnectionString = "file:appservice.db" } func (c *AppServiceAPI) Verify(configErrs *ConfigErrors, isMonolith bool) { - checkNotEmpty(configErrs, "app_service_api.listen", string(c.Listen)) - checkNotEmpty(configErrs, "app_service_api.bind", string(c.Bind)) + checkURL(configErrs, "app_service_api.internal_api.listen", string(c.InternalAPI.Listen)) + checkURL(configErrs, "app_service_api.internal_api.bind", string(c.InternalAPI.Connect)) checkNotEmpty(configErrs, "app_service_api.database.connection_string", string(c.Database.ConnectionString)) } diff --git a/internal/config/config_clientapi.go b/internal/config/config_clientapi.go index ae146fcb5..f7878276a 100644 --- a/internal/config/config_clientapi.go +++ b/internal/config/config_clientapi.go @@ -9,8 +9,8 @@ type ClientAPI struct { Matrix *Global `yaml:"-"` Derived *Derived `yaml:"-"` // TODO: Nuke Derived from orbit - Listen Address `yaml:"listen"` - Bind Address `yaml:"bind"` + InternalAPI InternalAPIOptions `yaml:"internal_api"` + ExternalAPI ExternalAPIOptions `yaml:"external_api"` // If set disables new users from registering (except via shared // secrets) @@ -37,8 +37,9 @@ type ClientAPI struct { } func (c *ClientAPI) Defaults() { - c.Listen = "localhost:7771" - c.Bind = "localhost:7771" + c.InternalAPI.Listen = "http://localhost:7771" + c.InternalAPI.Connect = "http://localhost:7771" + c.ExternalAPI.Listen = "http://[::]:8071" c.RegistrationSharedSecret = "" c.RecaptchaPublicKey = "" c.RecaptchaPrivateKey = "" @@ -49,8 +50,11 @@ func (c *ClientAPI) Defaults() { } func (c *ClientAPI) Verify(configErrs *ConfigErrors, isMonolith bool) { - checkNotEmpty(configErrs, "client_api.listen", string(c.Listen)) - checkNotEmpty(configErrs, "client_api.bind", string(c.Bind)) + checkURL(configErrs, "client_api.internal_api.listen", string(c.InternalAPI.Listen)) + checkURL(configErrs, "client_api.internal_api.connect", string(c.InternalAPI.Connect)) + if !isMonolith { + checkURL(configErrs, "client_api.external_api.listen", string(c.ExternalAPI.Listen)) + } if c.RecaptchaEnabled { checkNotEmpty(configErrs, "client_api.recaptcha_public_key", string(c.RecaptchaPublicKey)) checkNotEmpty(configErrs, "client_api.recaptcha_private_key", string(c.RecaptchaPrivateKey)) diff --git a/internal/config/config_currentstate.go b/internal/config/config_currentstate.go index 2687f7f5f..c07ebe158 100644 --- a/internal/config/config_currentstate.go +++ b/internal/config/config_currentstate.go @@ -3,8 +3,7 @@ package config type CurrentStateServer struct { Matrix *Global `yaml:"-"` - Listen Address `yaml:"listen"` - Bind Address `yaml:"bind"` + InternalAPI InternalAPIOptions `yaml:"internal_api"` // The CurrentState database stores the current state of all rooms. // It is accessed by the CurrentStateServer. @@ -12,14 +11,14 @@ type CurrentStateServer struct { } func (c *CurrentStateServer) Defaults() { - c.Listen = "localhost:7782" - c.Bind = "localhost:7782" + c.InternalAPI.Listen = "http://localhost:7782" + c.InternalAPI.Connect = "http://localhost:7782" c.Database.Defaults() c.Database.ConnectionString = "file:currentstate.db" } func (c *CurrentStateServer) Verify(configErrs *ConfigErrors, isMonolith bool) { - checkNotEmpty(configErrs, "current_state_server.listen", string(c.Listen)) - checkNotEmpty(configErrs, "current_state_server.bind", string(c.Bind)) + checkURL(configErrs, "current_state_server.internal_api.listen", string(c.InternalAPI.Listen)) + checkURL(configErrs, "current_state_server.internal_api.connect", string(c.InternalAPI.Connect)) checkNotEmpty(configErrs, "current_state_server.database.connection_string", string(c.Database.ConnectionString)) } diff --git a/internal/config/config_eduserver.go b/internal/config/config_eduserver.go index 027430415..a2ff36973 100644 --- a/internal/config/config_eduserver.go +++ b/internal/config/config_eduserver.go @@ -3,16 +3,15 @@ package config type EDUServer struct { Matrix *Global `yaml:"-"` - Listen Address `yaml:"listen"` - Bind Address `yaml:"bind"` + InternalAPI InternalAPIOptions `yaml:"internal_api"` } func (c *EDUServer) Defaults() { - c.Listen = "localhost:7778" - c.Bind = "localhost:7778" + c.InternalAPI.Listen = "http://localhost:7778" + c.InternalAPI.Connect = "http://localhost:7778" } func (c *EDUServer) Verify(configErrs *ConfigErrors, isMonolith bool) { - checkNotEmpty(configErrs, "edu_server.listen", string(c.Listen)) - checkNotEmpty(configErrs, "edu_server.bind", string(c.Bind)) + checkURL(configErrs, "edu_server.internal_api.listen", string(c.InternalAPI.Listen)) + checkURL(configErrs, "edu_server.internal_api.connect", string(c.InternalAPI.Connect)) } diff --git a/internal/config/config_federationapi.go b/internal/config/config_federationapi.go index d155ef254..727bfce2f 100644 --- a/internal/config/config_federationapi.go +++ b/internal/config/config_federationapi.go @@ -5,8 +5,8 @@ import "github.com/matrix-org/gomatrixserverlib" type FederationAPI struct { Matrix *Global `yaml:"-"` - Listen Address `yaml:"listen"` - Bind Address `yaml:"bind"` + InternalAPI InternalAPIOptions `yaml:"internal_api"` + ExternalAPI ExternalAPIOptions `yaml:"external_api"` // List of paths to X509 certificates used by the external federation listeners. // These are used to calculate the TLS fingerprints to publish for this server. @@ -21,13 +21,17 @@ type FederationAPI struct { } func (c *FederationAPI) Defaults() { - c.Listen = "localhost:7772" - c.Bind = "localhost:7772" + c.InternalAPI.Listen = "http://localhost:7772" + c.InternalAPI.Connect = "http://localhost:7772" + c.ExternalAPI.Listen = "http://[::]:8072" } func (c *FederationAPI) Verify(configErrs *ConfigErrors, isMonolith bool) { - checkNotEmpty(configErrs, "federation_api.listen", string(c.Listen)) - checkNotEmpty(configErrs, "federation_api.bind", string(c.Bind)) + checkURL(configErrs, "federation_api.internal_api.listen", string(c.InternalAPI.Listen)) + checkURL(configErrs, "federation_api.internal_api.connect", string(c.InternalAPI.Connect)) + if !isMonolith { + checkURL(configErrs, "federation_api.external_api.listen", string(c.ExternalAPI.Listen)) + } // TODO: not applicable always, e.g. in demos //checkNotZero(configErrs, "federation_api.federation_certificates", int64(len(c.FederationCertificatePaths))) } diff --git a/internal/config/config_federationsender.go b/internal/config/config_federationsender.go index 09d8287ba..84f5b6f40 100644 --- a/internal/config/config_federationsender.go +++ b/internal/config/config_federationsender.go @@ -3,8 +3,7 @@ package config type FederationSender struct { Matrix *Global `yaml:"-"` - Listen Address `yaml:"listen"` - Bind Address `yaml:"bind"` + InternalAPI InternalAPIOptions `yaml:"internal_api"` // The FederationSender database stores information used by the FederationSender // It is only accessed by the FederationSender. @@ -24,8 +23,8 @@ type FederationSender struct { } func (c *FederationSender) Defaults() { - c.Listen = "localhost:7775" - c.Bind = "localhost:7775" + c.InternalAPI.Listen = "http://localhost:7775" + c.InternalAPI.Connect = "http://localhost:7775" c.Database.Defaults() c.Database.ConnectionString = "file:federationsender.db" @@ -36,8 +35,8 @@ func (c *FederationSender) Defaults() { } func (c *FederationSender) Verify(configErrs *ConfigErrors, isMonolith bool) { - checkNotEmpty(configErrs, "federation_sender.listen", string(c.Listen)) - checkNotEmpty(configErrs, "federation_sender.bind", string(c.Bind)) + checkURL(configErrs, "federation_sender.internal_api.listen", string(c.InternalAPI.Listen)) + checkURL(configErrs, "federation_sender.internal_api.connect", string(c.InternalAPI.Connect)) checkNotEmpty(configErrs, "federation_sender.database.connection_string", string(c.Database.ConnectionString)) } diff --git a/internal/config/config_keyserver.go b/internal/config/config_keyserver.go index c0967a8ab..891623008 100644 --- a/internal/config/config_keyserver.go +++ b/internal/config/config_keyserver.go @@ -3,21 +3,20 @@ package config type KeyServer struct { Matrix *Global `yaml:"-"` - Listen Address `yaml:"listen"` - Bind Address `yaml:"bind"` + InternalAPI InternalAPIOptions `yaml:"internal_api"` Database DatabaseOptions `yaml:"database"` } func (c *KeyServer) Defaults() { - c.Listen = "localhost:7779" - c.Bind = "localhost:7779" + c.InternalAPI.Listen = "http://localhost:7779" + c.InternalAPI.Connect = "http://localhost:7779" c.Database.Defaults() c.Database.ConnectionString = "file:keyserver.db" } func (c *KeyServer) Verify(configErrs *ConfigErrors, isMonolith bool) { - checkNotEmpty(configErrs, "key_server.listen", string(c.Listen)) - checkNotEmpty(configErrs, "key_server.bind", string(c.Bind)) + checkURL(configErrs, "key_server.internal_api.listen", string(c.InternalAPI.Listen)) + checkURL(configErrs, "key_server.internal_api.bind", string(c.InternalAPI.Connect)) checkNotEmpty(configErrs, "key_server.database.connection_string", string(c.Database.ConnectionString)) } diff --git a/internal/config/config_mediaapi.go b/internal/config/config_mediaapi.go index 9a4d7e0a2..a9425b7be 100644 --- a/internal/config/config_mediaapi.go +++ b/internal/config/config_mediaapi.go @@ -7,8 +7,8 @@ import ( type MediaAPI struct { Matrix *Global `yaml:"-"` - Listen Address `yaml:"listen"` - Bind Address `yaml:"bind"` + InternalAPI InternalAPIOptions `yaml:"internal_api"` + ExternalAPI ExternalAPIOptions `yaml:"external_api"` // The MediaAPI database stores information about files uploaded and downloaded // by local users. It is only accessed by the MediaAPI. @@ -36,8 +36,9 @@ type MediaAPI struct { } func (c *MediaAPI) Defaults() { - c.Listen = "localhost:7774" - c.Bind = "localhost:7774" + c.InternalAPI.Listen = "http://localhost:7774" + c.InternalAPI.Connect = "http://localhost:7774" + c.ExternalAPI.Listen = "http://[::]:8074" c.Database.Defaults() c.Database.ConnectionString = "file:mediaapi.db" @@ -48,8 +49,11 @@ func (c *MediaAPI) Defaults() { } func (c *MediaAPI) Verify(configErrs *ConfigErrors, isMonolith bool) { - checkNotEmpty(configErrs, "media_api.listen", string(c.Listen)) - checkNotEmpty(configErrs, "media_api.bind", string(c.Bind)) + checkURL(configErrs, "media_api.internal_api.listen", string(c.InternalAPI.Listen)) + checkURL(configErrs, "media_api.internal_api.connect", string(c.InternalAPI.Connect)) + if !isMonolith { + checkURL(configErrs, "media_api.external_api.listen", string(c.ExternalAPI.Listen)) + } checkNotEmpty(configErrs, "media_api.database.connection_string", string(c.Database.ConnectionString)) checkNotEmpty(configErrs, "media_api.base_path", string(c.BasePath)) diff --git a/internal/config/config_roomserver.go b/internal/config/config_roomserver.go index 1a16e2b1f..2a1fc38b3 100644 --- a/internal/config/config_roomserver.go +++ b/internal/config/config_roomserver.go @@ -3,21 +3,20 @@ package config type RoomServer struct { Matrix *Global `yaml:"-"` - Listen Address `yaml:"listen"` - Bind Address `yaml:"bind"` + InternalAPI InternalAPIOptions `yaml:"internal_api"` Database DatabaseOptions `yaml:"database"` } func (c *RoomServer) Defaults() { - c.Listen = "localhost:7770" - c.Bind = "localhost:7770" + c.InternalAPI.Listen = "http://localhost:7770" + c.InternalAPI.Connect = "http://localhost:7770" c.Database.Defaults() c.Database.ConnectionString = "file:roomserver.db" } func (c *RoomServer) Verify(configErrs *ConfigErrors, isMonolith bool) { - checkNotEmpty(configErrs, "room_server.listen", string(c.Listen)) - checkNotEmpty(configErrs, "room_server.bind", string(c.Bind)) + checkURL(configErrs, "room_server.internal_api.listen", string(c.InternalAPI.Listen)) + checkURL(configErrs, "room_server.internal_ap.bind", string(c.InternalAPI.Connect)) checkNotEmpty(configErrs, "room_server.database.connection_string", string(c.Database.ConnectionString)) } diff --git a/internal/config/config_serverkey.go b/internal/config/config_serverkey.go index 78dc11947..40506d233 100644 --- a/internal/config/config_serverkey.go +++ b/internal/config/config_serverkey.go @@ -5,8 +5,7 @@ import "github.com/matrix-org/gomatrixserverlib" type ServerKeyAPI struct { Matrix *Global `yaml:"-"` - Listen Address `yaml:"listen"` - Bind Address `yaml:"bind"` + InternalAPI InternalAPIOptions `yaml:"internal_api"` // The ServerKey database caches the public keys of remote servers. // It may be accessed by the FederationAPI, the ClientAPI, and the MediaAPI. @@ -18,15 +17,15 @@ type ServerKeyAPI struct { } func (c *ServerKeyAPI) Defaults() { - c.Listen = "localhost:7780" - c.Bind = "localhost:7780" + c.InternalAPI.Listen = "http://localhost:7780" + c.InternalAPI.Connect = "http://localhost:7780" c.Database.Defaults() c.Database.ConnectionString = "file:serverkeyapi.db" } func (c *ServerKeyAPI) Verify(configErrs *ConfigErrors, isMonolith bool) { - checkNotEmpty(configErrs, "server_key_api.listen", string(c.Listen)) - checkNotEmpty(configErrs, "server_key_api.bind", string(c.Bind)) + checkURL(configErrs, "server_key_api.internal_api.listen", string(c.InternalAPI.Listen)) + checkURL(configErrs, "server_key_api.internal_api.bind", string(c.InternalAPI.Connect)) checkNotEmpty(configErrs, "server_key_api.database.connection_string", string(c.Database.ConnectionString)) } diff --git a/internal/config/config_syncapi.go b/internal/config/config_syncapi.go index 488f6658d..fc1bbcf82 100644 --- a/internal/config/config_syncapi.go +++ b/internal/config/config_syncapi.go @@ -3,21 +3,20 @@ package config type SyncAPI struct { Matrix *Global `yaml:"-"` - Listen Address `yaml:"listen"` - Bind Address `yaml:"bind"` + InternalAPI InternalAPIOptions `yaml:"internal_api"` Database DatabaseOptions `yaml:"database"` } func (c *SyncAPI) Defaults() { - c.Listen = "localhost:7773" - c.Bind = "localhost:7773" + c.InternalAPI.Listen = "http://localhost:7773" + c.InternalAPI.Connect = "http://localhost:7773" c.Database.Defaults() c.Database.ConnectionString = "file:syncapi.db" } func (c *SyncAPI) Verify(configErrs *ConfigErrors, isMonolith bool) { - checkNotEmpty(configErrs, "sync_api.listen", string(c.Listen)) - checkNotEmpty(configErrs, "sync_api.bind", string(c.Bind)) + checkURL(configErrs, "sync_api.internal_api.listen", string(c.InternalAPI.Listen)) + checkURL(configErrs, "sync_api.internal_api.bind", string(c.InternalAPI.Connect)) checkNotEmpty(configErrs, "sync_api.database", string(c.Database.ConnectionString)) } diff --git a/internal/config/config_userapi.go b/internal/config/config_userapi.go index f7da9e593..2cbd8a450 100644 --- a/internal/config/config_userapi.go +++ b/internal/config/config_userapi.go @@ -3,8 +3,7 @@ package config type UserAPI struct { Matrix *Global `yaml:"-"` - Listen Address `yaml:"listen"` - Bind Address `yaml:"bind"` + InternalAPI InternalAPIOptions `yaml:"internal_api"` // The Account database stores the login details and account information // for local users. It is accessed by the UserAPI. @@ -15,8 +14,8 @@ type UserAPI struct { } func (c *UserAPI) Defaults() { - c.Listen = "localhost:7781" - c.Bind = "localhost:7781" + c.InternalAPI.Listen = "http://localhost:7781" + c.InternalAPI.Connect = "http://localhost:7781" c.AccountDatabase.Defaults() c.DeviceDatabase.Defaults() c.AccountDatabase.ConnectionString = "file:userapi_accounts.db" @@ -24,8 +23,8 @@ func (c *UserAPI) Defaults() { } func (c *UserAPI) Verify(configErrs *ConfigErrors, isMonolith bool) { - checkNotEmpty(configErrs, "user_api.listen", string(c.Listen)) - checkNotEmpty(configErrs, "user_api.bind", string(c.Bind)) + checkURL(configErrs, "user_api.internal_api.listen", string(c.InternalAPI.Listen)) + checkURL(configErrs, "user_api.internal_api.connect", string(c.InternalAPI.Connect)) checkNotEmpty(configErrs, "user_api.account_database.connection_string", string(c.AccountDatabase.ConnectionString)) checkNotEmpty(configErrs, "user_api.device_database.connection_string", string(c.DeviceDatabase.ConnectionString)) } diff --git a/internal/httputil/httpapi.go b/internal/httputil/httpapi.go index 8f7723efa..86801c1e7 100644 --- a/internal/httputil/httpapi.go +++ b/internal/httputil/httpapi.go @@ -233,14 +233,24 @@ func (f *FederationWakeups) Wakeup(ctx context.Context, origin gomatrixserverlib } } -// SetupHTTPAPI registers an HTTP API mux under /api and sets up a metrics listener +// SetupHTTPAPI registers both internal and external HTTP APIs. func SetupHTTPAPI(servMux, publicApiMux, internalApiMux *mux.Router, cfg *config.Global, enableHTTPAPIs bool) { + SetupInternalHTTPAPI(servMux, internalApiMux, cfg, enableHTTPAPIs) + SetupExternalHTTPAPI(servMux, publicApiMux, cfg) +} + +// SetupInternalHTTPAPI registers internal APIs and metrics only. +func SetupInternalHTTPAPI(servMux, internalApiMux *mux.Router, cfg *config.Global, enableHTTPAPIs bool) { if cfg.Metrics.Enabled { servMux.Handle("/metrics", WrapHandlerInBasicAuth(promhttp.Handler(), cfg.Metrics.BasicAuth)) } if enableHTTPAPIs { servMux.Handle(InternalPathPrefix, internalApiMux) } +} + +// SetupExternalHTTPAPI registers public APIs only. +func SetupExternalHTTPAPI(servMux, publicApiMux *mux.Router, cfg *config.Global) { servMux.Handle(PublicPathPrefix, WrapHandlerInCORS(publicApiMux)) } diff --git a/internal/setup/base.go b/internal/setup/base.go index f59d136e5..0eb99dec2 100644 --- a/internal/setup/base.go +++ b/internal/setup/base.go @@ -19,6 +19,7 @@ import ( "io" "net/http" "net/url" + "sync" "time" currentstateAPI "github.com/matrix-org/dendrite/currentstateserver/api" @@ -266,37 +267,65 @@ func (b *BaseDendrite) CreateFederationClient() *gomatrixserverlib.FederationCli // SetupAndServeHTTP sets up the HTTP server to serve endpoints registered on // ApiMux under /api/ and adds a prometheus handler under /metrics. -func (b *BaseDendrite) SetupAndServeHTTP(bindaddr string, listenaddr string) { - // If a separate bind address is defined, listen on that. Otherwise use - // the listen address - var addr string - if bindaddr != "" { - addr = bindaddr - } else { - addr = listenaddr +func (b *BaseDendrite) SetupAndServeHTTP(internaladdr, externaladdr string) { + var wg sync.WaitGroup + + externalRouter := mux.NewRouter().SkipClean(true) + internalRouter := externalRouter + if internaladdr != externaladdr { + internalRouter = mux.NewRouter().SkipClean(true) } - serv := http.Server{ - Addr: addr, + externalServ := &http.Server{ + Addr: externaladdr, WriteTimeout: HTTPServerTimeout, + Handler: externalRouter, + } + internalServ := externalServ + if internaladdr != externaladdr { + internalServ = &http.Server{ + Addr: internaladdr, + WriteTimeout: HTTPServerTimeout, + Handler: internalRouter, + } } - httputil.SetupHTTPAPI( - b.BaseMux, + httputil.SetupExternalHTTPAPI( + externalRouter, b.PublicAPIMux, + &b.Cfg.Global, + ) + + httputil.SetupInternalHTTPAPI( + internalRouter, b.InternalAPIMux, &b.Cfg.Global, b.UseHTTPAPIs, ) - serv.Handler = b.BaseMux - logrus.Infof("Starting %s server on %s", b.componentName, serv.Addr) - err := serv.ListenAndServe() - if err != nil { - logrus.WithError(err).Fatal("failed to serve http") + wg.Add(1) + go func() { + defer wg.Done() + logrus.Infof("Starting %s external APIs on %s", b.componentName, externalServ.Addr) + if err := externalServ.ListenAndServe(); err != nil { + logrus.WithError(err).Fatal("failed to serve http") + } + logrus.Infof("Stopped %s external APIs on %s", b.componentName, externalServ.Addr) + }() + + if internaladdr != externaladdr { + wg.Add(1) + go func() { + defer wg.Done() + logrus.Infof("Starting %s internal APIs on %s", b.componentName, internalServ.Addr) + if err := internalServ.ListenAndServe(); err != nil { + logrus.WithError(err).Fatal("failed to serve http") + } + logrus.Infof("Stopped %s internal APIs on %s", b.componentName, internalServ.Addr) + }() } - logrus.Infof("Stopped %s server on %s", b.componentName, serv.Addr) + wg.Wait() } // setupKafka creates kafka consumer/producer pair from the config. diff --git a/internal/test/config.go b/internal/test/config.go index 43a5d1ff6..e2106de40 100644 --- a/internal/test/config.go +++ b/internal/test/config.go @@ -52,8 +52,8 @@ func MakeConfig(configDir, kafkaURI, database, host string, startPort int) (*con cfg.Defaults() port := startPort - assignAddress := func() config.Address { - result := config.Address(fmt.Sprintf("%s:%d", host, port)) + assignAddress := func() config.HTTPAddress { + result := config.HTTPAddress(fmt.Sprintf("http://%s:%d", host, port)) port++ return result } @@ -97,29 +97,29 @@ func MakeConfig(configDir, kafkaURI, database, host string, startPort int) (*con cfg.UserAPI.AccountDatabase.ConnectionString = config.DataSource(database) cfg.UserAPI.DeviceDatabase.ConnectionString = config.DataSource(database) - cfg.AppServiceAPI.Listen = assignAddress() - cfg.CurrentStateServer.Listen = assignAddress() - cfg.EDUServer.Listen = assignAddress() - cfg.FederationAPI.Listen = assignAddress() - cfg.FederationSender.Listen = assignAddress() - cfg.KeyServer.Listen = assignAddress() - cfg.MediaAPI.Listen = assignAddress() - cfg.RoomServer.Listen = assignAddress() - cfg.ServerKeyAPI.Listen = assignAddress() - cfg.SyncAPI.Listen = assignAddress() - cfg.UserAPI.Listen = assignAddress() + cfg.AppServiceAPI.InternalAPI.Listen = assignAddress() + cfg.CurrentStateServer.InternalAPI.Listen = assignAddress() + cfg.EDUServer.InternalAPI.Listen = assignAddress() + cfg.FederationAPI.InternalAPI.Listen = assignAddress() + cfg.FederationSender.InternalAPI.Listen = assignAddress() + cfg.KeyServer.InternalAPI.Listen = assignAddress() + cfg.MediaAPI.InternalAPI.Listen = assignAddress() + cfg.RoomServer.InternalAPI.Listen = assignAddress() + cfg.ServerKeyAPI.InternalAPI.Listen = assignAddress() + cfg.SyncAPI.InternalAPI.Listen = assignAddress() + cfg.UserAPI.InternalAPI.Listen = assignAddress() - cfg.AppServiceAPI.Bind = cfg.AppServiceAPI.Listen - cfg.CurrentStateServer.Bind = cfg.CurrentStateServer.Listen - cfg.EDUServer.Bind = cfg.EDUServer.Listen - cfg.FederationAPI.Bind = cfg.FederationAPI.Listen - cfg.FederationSender.Bind = cfg.FederationSender.Listen - cfg.KeyServer.Bind = cfg.KeyServer.Listen - cfg.MediaAPI.Bind = cfg.MediaAPI.Listen - cfg.RoomServer.Bind = cfg.RoomServer.Listen - cfg.ServerKeyAPI.Bind = cfg.ServerKeyAPI.Listen - cfg.SyncAPI.Bind = cfg.SyncAPI.Listen - cfg.UserAPI.Bind = cfg.UserAPI.Listen + cfg.AppServiceAPI.InternalAPI.Connect = cfg.AppServiceAPI.InternalAPI.Listen + cfg.CurrentStateServer.InternalAPI.Connect = cfg.CurrentStateServer.InternalAPI.Listen + cfg.EDUServer.InternalAPI.Connect = cfg.EDUServer.InternalAPI.Listen + cfg.FederationAPI.InternalAPI.Connect = cfg.FederationAPI.InternalAPI.Listen + cfg.FederationSender.InternalAPI.Connect = cfg.FederationSender.InternalAPI.Listen + cfg.KeyServer.InternalAPI.Connect = cfg.KeyServer.InternalAPI.Listen + cfg.MediaAPI.InternalAPI.Connect = cfg.MediaAPI.InternalAPI.Listen + cfg.RoomServer.InternalAPI.Connect = cfg.RoomServer.InternalAPI.Listen + cfg.ServerKeyAPI.InternalAPI.Connect = cfg.ServerKeyAPI.InternalAPI.Listen + cfg.SyncAPI.InternalAPI.Connect = cfg.SyncAPI.InternalAPI.Listen + cfg.UserAPI.InternalAPI.Connect = cfg.UserAPI.InternalAPI.Listen return &cfg, port, nil } diff --git a/internal/test/server.go b/internal/test/server.go index 57df21db4..ed4e7e28e 100644 --- a/internal/test/server.go +++ b/internal/test/server.go @@ -96,9 +96,9 @@ func InitDatabase(postgresDatabase, postgresContainerName string, databases []st func StartProxy(bindAddr string, cfg *config.Dendrite) (*exec.Cmd, chan error) { proxyArgs := []string{ "--bind-address", bindAddr, - "--sync-api-server-url", "http://" + string(cfg.SyncAPI.Listen), - "--client-api-server-url", "http://" + string(cfg.ClientAPI.Listen), - "--media-api-server-url", "http://" + string(cfg.MediaAPI.Listen), + "--sync-api-server-url", "http://" + string(cfg.SyncAPI.InternalAPI.Connect), + "--client-api-server-url", "http://" + string(cfg.ClientAPI.InternalAPI.Connect), + "--media-api-server-url", "http://" + string(cfg.MediaAPI.InternalAPI.Connect), "--tls-cert", "server.crt", "--tls-key", "server.key", }