github.com/dschalla/mattermost-server@v4.8.1-rc1+incompatible/app/server.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package app 5 6 import ( 7 "context" 8 "crypto/tls" 9 "io" 10 "io/ioutil" 11 "net" 12 "net/http" 13 "os" 14 "strings" 15 "time" 16 17 l4g "github.com/alecthomas/log4go" 18 "github.com/gorilla/handlers" 19 "github.com/gorilla/mux" 20 "github.com/pkg/errors" 21 "golang.org/x/crypto/acme/autocert" 22 23 "github.com/mattermost/mattermost-server/model" 24 "github.com/mattermost/mattermost-server/store" 25 "github.com/mattermost/mattermost-server/utils" 26 ) 27 28 type Server struct { 29 Store store.Store 30 WebSocketRouter *WebSocketRouter 31 Router *mux.Router 32 Server *http.Server 33 ListenAddr *net.TCPAddr 34 RateLimiter *RateLimiter 35 36 didFinishListen chan struct{} 37 } 38 39 var allowedMethods []string = []string{ 40 "POST", 41 "GET", 42 "OPTIONS", 43 "PUT", 44 "PATCH", 45 "DELETE", 46 } 47 48 type RecoveryLogger struct { 49 } 50 51 func (rl *RecoveryLogger) Println(i ...interface{}) { 52 l4g.Error("Please check the std error output for the stack trace") 53 l4g.Error(i) 54 } 55 56 type CorsWrapper struct { 57 config model.ConfigFunc 58 router *mux.Router 59 } 60 61 func (cw *CorsWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) { 62 if allowed := *cw.config().ServiceSettings.AllowCorsFrom; allowed != "" { 63 if utils.CheckOrigin(r, allowed) { 64 w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) 65 66 if r.Method == "OPTIONS" { 67 w.Header().Set( 68 "Access-Control-Allow-Methods", 69 strings.Join(allowedMethods, ", ")) 70 71 w.Header().Set( 72 "Access-Control-Allow-Headers", 73 r.Header.Get("Access-Control-Request-Headers")) 74 } 75 } 76 } 77 78 if r.Method == "OPTIONS" { 79 return 80 } 81 82 cw.router.ServeHTTP(w, r) 83 } 84 85 const TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN = time.Second 86 87 type VaryBy struct { 88 useIP bool 89 useAuth bool 90 } 91 92 func (m *VaryBy) Key(r *http.Request) string { 93 key := "" 94 95 if m.useAuth { 96 token, tokenLocation := ParseAuthTokenFromRequest(r) 97 if tokenLocation != TokenLocationNotFound { 98 key += token 99 } else if m.useIP { // If we don't find an authentication token and IP based is enabled, fall back to IP 100 key += utils.GetIpAddress(r) 101 } 102 } else if m.useIP { // Only if Auth based is not enabed do we use a plain IP based 103 key = utils.GetIpAddress(r) 104 } 105 106 return key 107 } 108 109 func redirectHTTPToHTTPS(w http.ResponseWriter, r *http.Request) { 110 if r.Host == "" { 111 http.Error(w, "Not Found", http.StatusNotFound) 112 } 113 114 url := r.URL 115 url.Host = r.Host 116 url.Scheme = "https" 117 http.Redirect(w, r, url.String(), http.StatusFound) 118 } 119 120 func (a *App) StartServer() error { 121 l4g.Info(utils.T("api.server.start_server.starting.info")) 122 123 var handler http.Handler = &CorsWrapper{a.Config, a.Srv.Router} 124 125 if *a.Config().RateLimitSettings.Enable { 126 l4g.Info(utils.T("api.server.start_server.rate.info")) 127 128 rateLimiter, err := NewRateLimiter(&a.Config().RateLimitSettings) 129 if err != nil { 130 return err 131 } 132 133 a.Srv.RateLimiter = rateLimiter 134 handler = rateLimiter.RateLimitHandler(handler) 135 } 136 137 a.Srv.Server = &http.Server{ 138 Handler: handlers.RecoveryHandler(handlers.RecoveryLogger(&RecoveryLogger{}), handlers.PrintRecoveryStack(true))(handler), 139 ReadTimeout: time.Duration(*a.Config().ServiceSettings.ReadTimeout) * time.Second, 140 WriteTimeout: time.Duration(*a.Config().ServiceSettings.WriteTimeout) * time.Second, 141 } 142 143 addr := *a.Config().ServiceSettings.ListenAddress 144 if addr == "" { 145 if *a.Config().ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS { 146 addr = ":https" 147 } else { 148 addr = ":http" 149 } 150 } 151 152 listener, err := net.Listen("tcp", addr) 153 if err != nil { 154 errors.Wrapf(err, utils.T("api.server.start_server.starting.critical"), err) 155 return err 156 } 157 a.Srv.ListenAddr = listener.Addr().(*net.TCPAddr) 158 159 l4g.Info(utils.T("api.server.start_server.listening.info"), listener.Addr().String()) 160 161 // Migration from old let's encrypt library 162 if *a.Config().ServiceSettings.UseLetsEncrypt { 163 if stat, err := os.Stat(*a.Config().ServiceSettings.LetsEncryptCertificateCacheFile); err == nil && !stat.IsDir() { 164 os.Remove(*a.Config().ServiceSettings.LetsEncryptCertificateCacheFile) 165 } 166 } 167 168 m := &autocert.Manager{ 169 Cache: autocert.DirCache(*a.Config().ServiceSettings.LetsEncryptCertificateCacheFile), 170 Prompt: autocert.AcceptTOS, 171 } 172 173 if *a.Config().ServiceSettings.Forward80To443 { 174 if host, _, err := net.SplitHostPort(addr); err != nil { 175 l4g.Error("Unable to setup forwarding: " + err.Error()) 176 } else { 177 httpListenAddress := net.JoinHostPort(host, "http") 178 179 if *a.Config().ServiceSettings.UseLetsEncrypt { 180 go http.ListenAndServe(httpListenAddress, m.HTTPHandler(nil)) 181 } else { 182 go func() { 183 redirectListener, err := net.Listen("tcp", httpListenAddress) 184 if err != nil { 185 l4g.Error("Unable to setup forwarding: " + err.Error()) 186 return 187 } 188 defer redirectListener.Close() 189 190 http.Serve(redirectListener, http.HandlerFunc(redirectHTTPToHTTPS)) 191 }() 192 } 193 } 194 } 195 196 a.Srv.didFinishListen = make(chan struct{}) 197 go func() { 198 var err error 199 if *a.Config().ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS { 200 if *a.Config().ServiceSettings.UseLetsEncrypt { 201 202 tlsConfig := &tls.Config{ 203 GetCertificate: m.GetCertificate, 204 } 205 206 tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2") 207 208 a.Srv.Server.TLSConfig = tlsConfig 209 err = a.Srv.Server.ServeTLS(listener, "", "") 210 } else { 211 err = a.Srv.Server.ServeTLS(listener, *a.Config().ServiceSettings.TLSCertFile, *a.Config().ServiceSettings.TLSKeyFile) 212 } 213 } else { 214 err = a.Srv.Server.Serve(listener) 215 } 216 if err != nil && err != http.ErrServerClosed { 217 l4g.Critical(utils.T("api.server.start_server.starting.critical"), err) 218 time.Sleep(time.Second) 219 } 220 close(a.Srv.didFinishListen) 221 }() 222 223 return nil 224 } 225 226 type tcpKeepAliveListener struct { 227 *net.TCPListener 228 } 229 230 func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { 231 tc, err := ln.AcceptTCP() 232 if err != nil { 233 return 234 } 235 tc.SetKeepAlive(true) 236 tc.SetKeepAlivePeriod(3 * time.Minute) 237 return tc, nil 238 } 239 240 func (a *App) Listen(addr string) (net.Listener, error) { 241 if addr == "" { 242 addr = ":http" 243 } 244 ln, err := net.Listen("tcp", addr) 245 if err != nil { 246 return nil, err 247 } 248 return tcpKeepAliveListener{ln.(*net.TCPListener)}, nil 249 } 250 251 func (a *App) StopServer() { 252 if a.Srv.Server != nil { 253 ctx, cancel := context.WithTimeout(context.Background(), TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN) 254 defer cancel() 255 didShutdown := false 256 for a.Srv.didFinishListen != nil && !didShutdown { 257 if err := a.Srv.Server.Shutdown(ctx); err != nil { 258 l4g.Warn(err.Error()) 259 } 260 timer := time.NewTimer(time.Millisecond * 50) 261 select { 262 case <-a.Srv.didFinishListen: 263 didShutdown = true 264 case <-timer.C: 265 } 266 timer.Stop() 267 } 268 a.Srv.Server.Close() 269 a.Srv.Server = nil 270 } 271 } 272 273 func (a *App) OriginChecker() func(*http.Request) bool { 274 if allowed := *a.Config().ServiceSettings.AllowCorsFrom; allowed != "" { 275 return utils.OriginChecker(allowed) 276 } 277 return nil 278 } 279 280 // This is required to re-use the underlying connection and not take up file descriptors 281 func consumeAndClose(r *http.Response) { 282 if r.Body != nil { 283 io.Copy(ioutil.Discard, r.Body) 284 r.Body.Close() 285 } 286 }