diff options
Diffstat (limited to 'app/server.go')
-rw-r--r-- | app/server.go | 124 |
1 files changed, 95 insertions, 29 deletions
diff --git a/app/server.go b/app/server.go index 5f955dd65..d686c1f24 100644 --- a/app/server.go +++ b/app/server.go @@ -4,7 +4,10 @@ package app import ( + "context" "crypto/tls" + "io" + "io/ioutil" "net" "net/http" "strings" @@ -14,13 +17,11 @@ import ( "github.com/gorilla/handlers" "github.com/gorilla/mux" "github.com/rsc/letsencrypt" - "github.com/tylerb/graceful" "gopkg.in/throttled/throttled.v2" "gopkg.in/throttled/throttled.v2/store/memstore" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/store/sqlstore" "github.com/mattermost/mattermost-server/utils" ) @@ -28,7 +29,10 @@ type Server struct { Store store.Store WebSocketRouter *WebSocketRouter Router *mux.Router - GracefulServer *graceful.Server + Server *http.Server + ListenAddr *net.TCPAddr + + didFinishListen chan struct{} } var allowedMethods []string = []string{ @@ -78,16 +82,6 @@ func (cw *CorsWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) { const TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN = time.Second -func (a *App) NewServer() { - l4g.Info(utils.T("api.server.new_server.init.info")) - - a.Srv = &Server{} -} - -func (a *App) InitStores() { - a.Srv.Store = store.NewLayeredStore(sqlstore.NewSqlSupplier(a.Metrics), a.Metrics, a.Cluster) -} - type VaryBy struct{} func (m *VaryBy) Key(r *http.Request) string { @@ -161,30 +155,45 @@ func (a *App) StartServer() { handler = httpRateLimiter.RateLimit(handler) } - a.Srv.GracefulServer = &graceful.Server{ - Timeout: TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN, - Server: &http.Server{ - Addr: *utils.Cfg.ServiceSettings.ListenAddress, - Handler: handlers.RecoveryHandler(handlers.RecoveryLogger(&RecoveryLogger{}), handlers.PrintRecoveryStack(true))(handler), - ReadTimeout: time.Duration(*utils.Cfg.ServiceSettings.ReadTimeout) * time.Second, - WriteTimeout: time.Duration(*utils.Cfg.ServiceSettings.WriteTimeout) * time.Second, - }, + a.Srv.Server = &http.Server{ + Handler: handlers.RecoveryHandler(handlers.RecoveryLogger(&RecoveryLogger{}), handlers.PrintRecoveryStack(true))(handler), + ReadTimeout: time.Duration(*utils.Cfg.ServiceSettings.ReadTimeout) * time.Second, + WriteTimeout: time.Duration(*utils.Cfg.ServiceSettings.WriteTimeout) * time.Second, + } + + addr := *a.Config().ServiceSettings.ListenAddress + if addr == "" { + if *utils.Cfg.ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS { + addr = ":https" + } else { + addr = ":http" + } + } + + listener, err := net.Listen("tcp", addr) + if err != nil { + l4g.Critical(utils.T("api.server.start_server.starting.critical"), err) + return } - l4g.Info(utils.T("api.server.start_server.listening.info"), *utils.Cfg.ServiceSettings.ListenAddress) + a.Srv.ListenAddr = listener.Addr().(*net.TCPAddr) + + l4g.Info(utils.T("api.server.start_server.listening.info"), listener.Addr().String()) if *utils.Cfg.ServiceSettings.Forward80To443 { go func() { - listener, err := net.Listen("tcp", ":80") + redirectListener, err := net.Listen("tcp", ":80") if err != nil { + listener.Close() l4g.Error("Unable to setup forwarding") return } - defer listener.Close() + defer redirectListener.Close() - http.Serve(listener, http.HandlerFunc(redirectHTTPToHTTPS)) + http.Serve(redirectListener, http.HandlerFunc(redirectHTTPToHTTPS)) }() } + a.Srv.didFinishListen = make(chan struct{}) go func() { var err error if *utils.Cfg.ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS { @@ -198,16 +207,73 @@ func (a *App) StartServer() { tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2") - err = a.Srv.GracefulServer.ListenAndServeTLSConfig(tlsConfig) + a.Srv.Server.TLSConfig = tlsConfig + err = a.Srv.Server.ServeTLS(listener, "", "") } else { - err = a.Srv.GracefulServer.ListenAndServeTLS(*utils.Cfg.ServiceSettings.TLSCertFile, *utils.Cfg.ServiceSettings.TLSKeyFile) + err = a.Srv.Server.ServeTLS(listener, *utils.Cfg.ServiceSettings.TLSCertFile, *utils.Cfg.ServiceSettings.TLSKeyFile) } } else { - err = a.Srv.GracefulServer.ListenAndServe() + err = a.Srv.Server.Serve(listener) } - if err != nil { + if err != nil && err != http.ErrServerClosed { l4g.Critical(utils.T("api.server.start_server.starting.critical"), err) time.Sleep(time.Second) } + close(a.Srv.didFinishListen) }() } + +type tcpKeepAliveListener struct { + *net.TCPListener +} + +func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { + tc, err := ln.AcceptTCP() + if err != nil { + return + } + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(3 * time.Minute) + return tc, nil +} + +func (a *App) Listen(addr string) (net.Listener, error) { + if addr == "" { + addr = ":http" + } + ln, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + return tcpKeepAliveListener{ln.(*net.TCPListener)}, nil +} + +func (a *App) StopServer() { + if a.Srv.Server != nil { + ctx, cancel := context.WithTimeout(context.Background(), TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN) + defer cancel() + didShutdown := false + for a.Srv.didFinishListen != nil && !didShutdown { + if err := a.Srv.Server.Shutdown(ctx); err != nil { + l4g.Warn(err.Error()) + } + timer := time.NewTimer(time.Millisecond * 50) + select { + case <-a.Srv.didFinishListen: + didShutdown = true + case <-timer.C: + } + timer.Stop() + } + a.Srv.Server.Close() + a.Srv.Server = nil + } +} + +// This is required to re-use the underlying connection and not take up file descriptors +func consumeAndClose(r *http.Response) { + if r.Body != nil { + io.Copy(ioutil.Discard, r.Body) + r.Body.Close() + } +} |