2017-04-12 08:27:57 -04:00
|
|
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
2015-06-14 23:53:32 -08:00
|
|
|
// See License.txt for license information.
|
|
|
|
|
|
2017-01-13 13:53:37 -05:00
|
|
|
package app
|
2015-06-14 23:53:32 -08:00
|
|
|
|
|
|
|
|
import (
|
2017-10-16 08:09:43 -07:00
|
|
|
"context"
|
2016-10-03 16:03:15 -04:00
|
|
|
"crypto/tls"
|
2017-10-12 08:00:53 -07:00
|
|
|
"io"
|
|
|
|
|
"io/ioutil"
|
2016-10-03 16:03:15 -04:00
|
|
|
"net"
|
2016-09-26 12:56:12 -04:00
|
|
|
"net/http"
|
2018-01-30 10:12:42 -08:00
|
|
|
"os"
|
2016-09-26 12:56:12 -04:00
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
2016-01-11 09:12:51 -06:00
|
|
|
l4g "github.com/alecthomas/log4go"
|
2016-04-21 22:37:01 -07:00
|
|
|
"github.com/gorilla/handlers"
|
2015-06-14 23:53:32 -08:00
|
|
|
"github.com/gorilla/mux"
|
2018-02-07 13:41:15 +05:30
|
|
|
"github.com/pkg/errors"
|
2018-01-30 10:12:42 -08:00
|
|
|
"golang.org/x/crypto/acme/autocert"
|
2017-05-24 07:55:52 -07:00
|
|
|
|
2017-09-06 23:05:10 -07:00
|
|
|
"github.com/mattermost/mattermost-server/model"
|
|
|
|
|
"github.com/mattermost/mattermost-server/store"
|
|
|
|
|
"github.com/mattermost/mattermost-server/utils"
|
2015-06-14 23:53:32 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Server struct {
|
2017-01-13 13:53:37 -05:00
|
|
|
Store store.Store
|
|
|
|
|
WebSocketRouter *WebSocketRouter
|
|
|
|
|
Router *mux.Router
|
2017-10-16 08:09:43 -07:00
|
|
|
Server *http.Server
|
|
|
|
|
ListenAddr *net.TCPAddr
|
2018-01-31 09:49:15 -08:00
|
|
|
RateLimiter *RateLimiter
|
2017-10-16 14:02:33 -07:00
|
|
|
|
|
|
|
|
didFinishListen chan struct{}
|
2017-01-13 13:53:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var allowedMethods []string = []string{
|
|
|
|
|
"POST",
|
|
|
|
|
"GET",
|
|
|
|
|
"OPTIONS",
|
|
|
|
|
"PUT",
|
|
|
|
|
"PATCH",
|
|
|
|
|
"DELETE",
|
2015-06-14 23:53:32 -08:00
|
|
|
}
|
|
|
|
|
|
2017-05-24 07:55:52 -07:00
|
|
|
type RecoveryLogger struct {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (rl *RecoveryLogger) Println(i ...interface{}) {
|
|
|
|
|
l4g.Error("Please check the std error output for the stack trace")
|
|
|
|
|
l4g.Error(i)
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-01 22:12:05 -03:00
|
|
|
type CorsWrapper struct {
|
2017-10-26 14:21:22 -05:00
|
|
|
config model.ConfigFunc
|
2016-03-01 22:12:05 -03:00
|
|
|
router *mux.Router
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-13 13:53:37 -05:00
|
|
|
func (cw *CorsWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
2017-11-22 15:58:03 -06:00
|
|
|
if allowed := *cw.config().ServiceSettings.AllowCorsFrom; allowed != "" {
|
|
|
|
|
if utils.CheckOrigin(r, allowed) {
|
2017-07-13 14:02:33 -07:00
|
|
|
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
2017-01-13 13:53:37 -05:00
|
|
|
|
|
|
|
|
if r.Method == "OPTIONS" {
|
|
|
|
|
w.Header().Set(
|
|
|
|
|
"Access-Control-Allow-Methods",
|
|
|
|
|
strings.Join(allowedMethods, ", "))
|
|
|
|
|
|
|
|
|
|
w.Header().Set(
|
|
|
|
|
"Access-Control-Allow-Headers",
|
|
|
|
|
r.Header.Get("Access-Control-Request-Headers"))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.Method == "OPTIONS" {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cw.router.ServeHTTP(w, r)
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-03 16:03:15 -04:00
|
|
|
const TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN = time.Second
|
|
|
|
|
|
|
|
|
|
func redirectHTTPToHTTPS(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
if r.Host == "" {
|
|
|
|
|
http.Error(w, "Not Found", http.StatusNotFound)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
url := r.URL
|
|
|
|
|
url.Host = r.Host
|
|
|
|
|
url.Scheme = "https"
|
|
|
|
|
http.Redirect(w, r, url.String(), http.StatusFound)
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-07 13:41:15 +05:30
|
|
|
func (a *App) StartServer() error {
|
2016-01-22 01:37:11 -03:00
|
|
|
l4g.Info(utils.T("api.server.start_server.starting.info"))
|
2015-07-29 01:26:10 -08:00
|
|
|
|
2017-10-26 14:21:22 -05:00
|
|
|
var handler http.Handler = &CorsWrapper{a.Config, a.Srv.Router}
|
2015-07-29 01:26:10 -08:00
|
|
|
|
2017-10-18 15:36:43 -07:00
|
|
|
if *a.Config().RateLimitSettings.Enable {
|
2016-01-22 01:37:11 -03:00
|
|
|
l4g.Info(utils.T("api.server.start_server.rate.info"))
|
2015-07-29 01:26:10 -08:00
|
|
|
|
2018-02-06 10:57:34 +05:30
|
|
|
rateLimiter, err := NewRateLimiter(&a.Config().RateLimitSettings)
|
|
|
|
|
if err != nil {
|
2018-02-07 13:41:15 +05:30
|
|
|
return err
|
2018-02-06 10:57:34 +05:30
|
|
|
}
|
2015-07-29 01:26:10 -08:00
|
|
|
|
2018-02-06 10:57:34 +05:30
|
|
|
a.Srv.RateLimiter = rateLimiter
|
|
|
|
|
handler = rateLimiter.RateLimitHandler(handler)
|
2015-07-29 01:26:10 -08:00
|
|
|
}
|
|
|
|
|
|
2017-10-16 08:09:43 -07:00
|
|
|
a.Srv.Server = &http.Server{
|
|
|
|
|
Handler: handlers.RecoveryHandler(handlers.RecoveryLogger(&RecoveryLogger{}), handlers.PrintRecoveryStack(true))(handler),
|
2017-10-18 15:36:43 -07:00
|
|
|
ReadTimeout: time.Duration(*a.Config().ServiceSettings.ReadTimeout) * time.Second,
|
|
|
|
|
WriteTimeout: time.Duration(*a.Config().ServiceSettings.WriteTimeout) * time.Second,
|
2016-10-03 16:03:15 -04:00
|
|
|
}
|
2017-10-16 08:09:43 -07:00
|
|
|
|
|
|
|
|
addr := *a.Config().ServiceSettings.ListenAddress
|
|
|
|
|
if addr == "" {
|
2017-10-18 15:36:43 -07:00
|
|
|
if *a.Config().ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS {
|
2017-10-16 08:09:43 -07:00
|
|
|
addr = ":https"
|
|
|
|
|
} else {
|
|
|
|
|
addr = ":http"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
listener, err := net.Listen("tcp", addr)
|
|
|
|
|
if err != nil {
|
2018-02-07 13:41:15 +05:30
|
|
|
errors.Wrapf(err, utils.T("api.server.start_server.starting.critical"), err)
|
|
|
|
|
return err
|
2017-10-16 08:09:43 -07:00
|
|
|
}
|
|
|
|
|
a.Srv.ListenAddr = listener.Addr().(*net.TCPAddr)
|
|
|
|
|
|
|
|
|
|
l4g.Info(utils.T("api.server.start_server.listening.info"), listener.Addr().String())
|
2016-10-03 16:03:15 -04:00
|
|
|
|
2018-01-30 10:12:42 -08:00
|
|
|
// Migration from old let's encrypt library
|
|
|
|
|
if *a.Config().ServiceSettings.UseLetsEncrypt {
|
|
|
|
|
if stat, err := os.Stat(*a.Config().ServiceSettings.LetsEncryptCertificateCacheFile); err == nil && !stat.IsDir() {
|
|
|
|
|
os.Remove(*a.Config().ServiceSettings.LetsEncryptCertificateCacheFile)
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-10-03 16:03:15 -04:00
|
|
|
|
2018-01-30 10:12:42 -08:00
|
|
|
m := &autocert.Manager{
|
|
|
|
|
Cache: autocert.DirCache(*a.Config().ServiceSettings.LetsEncryptCertificateCacheFile),
|
|
|
|
|
Prompt: autocert.AcceptTOS,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if *a.Config().ServiceSettings.Forward80To443 {
|
2018-02-23 15:33:36 -08:00
|
|
|
if host, _, err := net.SplitHostPort(addr); err != nil {
|
|
|
|
|
l4g.Error("Unable to setup forwarding: " + err.Error())
|
2018-01-30 10:12:42 -08:00
|
|
|
} else {
|
2018-02-23 15:33:36 -08:00
|
|
|
httpListenAddress := net.JoinHostPort(host, "http")
|
|
|
|
|
|
|
|
|
|
if *a.Config().ServiceSettings.UseLetsEncrypt {
|
|
|
|
|
go http.ListenAndServe(httpListenAddress, m.HTTPHandler(nil))
|
|
|
|
|
} else {
|
|
|
|
|
go func() {
|
|
|
|
|
redirectListener, err := net.Listen("tcp", httpListenAddress)
|
|
|
|
|
if err != nil {
|
|
|
|
|
l4g.Error("Unable to setup forwarding: " + err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
defer redirectListener.Close()
|
|
|
|
|
|
|
|
|
|
http.Serve(redirectListener, http.HandlerFunc(redirectHTTPToHTTPS))
|
|
|
|
|
}()
|
|
|
|
|
}
|
2018-01-30 10:12:42 -08:00
|
|
|
}
|
2016-10-03 16:03:15 -04:00
|
|
|
}
|
|
|
|
|
|
2017-10-16 14:02:33 -07:00
|
|
|
a.Srv.didFinishListen = make(chan struct{})
|
2015-06-14 23:53:32 -08:00
|
|
|
go func() {
|
2016-10-03 16:03:15 -04:00
|
|
|
var err error
|
2017-10-18 15:36:43 -07:00
|
|
|
if *a.Config().ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS {
|
|
|
|
|
if *a.Config().ServiceSettings.UseLetsEncrypt {
|
2016-10-03 16:03:15 -04:00
|
|
|
|
|
|
|
|
tlsConfig := &tls.Config{
|
|
|
|
|
GetCertificate: m.GetCertificate,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")
|
|
|
|
|
|
2017-10-16 08:09:43 -07:00
|
|
|
a.Srv.Server.TLSConfig = tlsConfig
|
|
|
|
|
err = a.Srv.Server.ServeTLS(listener, "", "")
|
2016-10-03 16:03:15 -04:00
|
|
|
} else {
|
2017-10-18 15:36:43 -07:00
|
|
|
err = a.Srv.Server.ServeTLS(listener, *a.Config().ServiceSettings.TLSCertFile, *a.Config().ServiceSettings.TLSKeyFile)
|
2016-10-03 16:03:15 -04:00
|
|
|
}
|
|
|
|
|
} else {
|
2017-10-16 08:09:43 -07:00
|
|
|
err = a.Srv.Server.Serve(listener)
|
2016-10-03 16:03:15 -04:00
|
|
|
}
|
2017-10-16 08:09:43 -07:00
|
|
|
if err != nil && err != http.ErrServerClosed {
|
2016-01-22 01:37:11 -03:00
|
|
|
l4g.Critical(utils.T("api.server.start_server.starting.critical"), err)
|
2015-06-14 23:53:32 -08:00
|
|
|
time.Sleep(time.Second)
|
|
|
|
|
}
|
2017-10-16 14:02:33 -07:00
|
|
|
close(a.Srv.didFinishListen)
|
2015-06-14 23:53:32 -08:00
|
|
|
}()
|
2018-02-07 13:41:15 +05:30
|
|
|
|
|
|
|
|
return nil
|
2015-06-14 23:53:32 -08:00
|
|
|
}
|
2017-10-09 14:59:48 -07:00
|
|
|
|
|
|
|
|
func (a *App) StopServer() {
|
2017-10-16 08:09:43 -07:00
|
|
|
if a.Srv.Server != nil {
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN)
|
|
|
|
|
defer cancel()
|
2017-10-16 14:02:33 -07:00
|
|
|
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()
|
2017-10-16 08:09:43 -07:00
|
|
|
}
|
|
|
|
|
a.Srv.Server.Close()
|
|
|
|
|
a.Srv.Server = nil
|
2017-10-09 14:59:48 -07:00
|
|
|
}
|
|
|
|
|
}
|
2017-10-12 08:00:53 -07:00
|
|
|
|
2017-11-22 15:58:03 -06:00
|
|
|
func (a *App) OriginChecker() func(*http.Request) bool {
|
|
|
|
|
if allowed := *a.Config().ServiceSettings.AllowCorsFrom; allowed != "" {
|
|
|
|
|
return utils.OriginChecker(allowed)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-12 08:00:53 -07:00
|
|
|
// 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()
|
|
|
|
|
}
|
|
|
|
|
}
|