mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
* Implementing structured logging
* Changes to en.json to allow refactor to run.
* Fixing global logger
* Structured logger initalization.
* Add caller.
* Do some log redirection.
* Auto refactor
* Cleaning up l4g reference and removing dependancy.
* Removing junk.
* Copyright headers.
* Fixing tests
* Revert "Changes to en.json to allow refactor to run."
This reverts commit fd8249e99b.
* Fixing some auto refactor strangeness and typo.
* Making keys more human readable.
255 lines
6.7 KiB
Go
255 lines
6.7 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gorilla/handlers"
|
|
"github.com/gorilla/mux"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/crypto/acme/autocert"
|
|
|
|
"github.com/mattermost/mattermost-server/mlog"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
"github.com/mattermost/mattermost-server/store"
|
|
"github.com/mattermost/mattermost-server/utils"
|
|
)
|
|
|
|
type Server struct {
|
|
Store store.Store
|
|
WebSocketRouter *WebSocketRouter
|
|
Router *mux.Router
|
|
Server *http.Server
|
|
ListenAddr *net.TCPAddr
|
|
RateLimiter *RateLimiter
|
|
|
|
didFinishListen chan struct{}
|
|
}
|
|
|
|
var allowedMethods []string = []string{
|
|
"POST",
|
|
"GET",
|
|
"OPTIONS",
|
|
"PUT",
|
|
"PATCH",
|
|
"DELETE",
|
|
}
|
|
|
|
type RecoveryLogger struct {
|
|
}
|
|
|
|
func (rl *RecoveryLogger) Println(i ...interface{}) {
|
|
mlog.Error("Please check the std error output for the stack trace")
|
|
mlog.Error(fmt.Sprint(i))
|
|
}
|
|
|
|
type CorsWrapper struct {
|
|
config model.ConfigFunc
|
|
router *mux.Router
|
|
}
|
|
|
|
func (cw *CorsWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
if allowed := *cw.config().ServiceSettings.AllowCorsFrom; allowed != "" {
|
|
if utils.CheckOrigin(r, allowed) {
|
|
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
|
|
|
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)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func (a *App) StartServer() error {
|
|
mlog.Info("Starting Server...")
|
|
|
|
var handler http.Handler = &CorsWrapper{a.Config, a.Srv.Router}
|
|
|
|
if *a.Config().RateLimitSettings.Enable {
|
|
mlog.Info("RateLimiter is enabled")
|
|
|
|
rateLimiter, err := NewRateLimiter(&a.Config().RateLimitSettings)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
a.Srv.RateLimiter = rateLimiter
|
|
handler = rateLimiter.RateLimitHandler(handler)
|
|
}
|
|
|
|
a.Srv.Server = &http.Server{
|
|
Handler: handlers.RecoveryHandler(handlers.RecoveryLogger(&RecoveryLogger{}), handlers.PrintRecoveryStack(true))(handler),
|
|
ReadTimeout: time.Duration(*a.Config().ServiceSettings.ReadTimeout) * time.Second,
|
|
WriteTimeout: time.Duration(*a.Config().ServiceSettings.WriteTimeout) * time.Second,
|
|
ErrorLog: a.Log.StdLog(mlog.String("source", "httpserver")),
|
|
}
|
|
|
|
addr := *a.Config().ServiceSettings.ListenAddress
|
|
if addr == "" {
|
|
if *a.Config().ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS {
|
|
addr = ":https"
|
|
} else {
|
|
addr = ":http"
|
|
}
|
|
}
|
|
|
|
listener, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
errors.Wrapf(err, utils.T("api.server.start_server.starting.critical"), err)
|
|
return err
|
|
}
|
|
a.Srv.ListenAddr = listener.Addr().(*net.TCPAddr)
|
|
|
|
mlog.Info(fmt.Sprintf("Server is listening on %v", listener.Addr().String()))
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
m := &autocert.Manager{
|
|
Cache: autocert.DirCache(*a.Config().ServiceSettings.LetsEncryptCertificateCacheFile),
|
|
Prompt: autocert.AcceptTOS,
|
|
}
|
|
|
|
if *a.Config().ServiceSettings.Forward80To443 {
|
|
if host, port, err := net.SplitHostPort(addr); err != nil {
|
|
mlog.Error("Unable to setup forwarding: " + err.Error())
|
|
} else if port != "443" {
|
|
return fmt.Errorf(utils.T("api.server.start_server.forward80to443.enabled_but_listening_on_wrong_port"), port)
|
|
} else {
|
|
httpListenAddress := net.JoinHostPort(host, "http")
|
|
|
|
if *a.Config().ServiceSettings.UseLetsEncrypt {
|
|
server := &http.Server{
|
|
Addr: httpListenAddress,
|
|
Handler: m.HTTPHandler(nil),
|
|
ErrorLog: a.Log.StdLog(mlog.String("source", "le_forwarder_server")),
|
|
}
|
|
go server.ListenAndServe()
|
|
} else {
|
|
go func() {
|
|
redirectListener, err := net.Listen("tcp", httpListenAddress)
|
|
if err != nil {
|
|
mlog.Error("Unable to setup forwarding: " + err.Error())
|
|
return
|
|
}
|
|
defer redirectListener.Close()
|
|
|
|
server := &http.Server{
|
|
Handler: handler,
|
|
ErrorLog: a.Log.StdLog(mlog.String("source", "forwarder_server")),
|
|
}
|
|
server.Serve(redirectListener)
|
|
}()
|
|
}
|
|
}
|
|
} else if *a.Config().ServiceSettings.UseLetsEncrypt {
|
|
return errors.New(utils.T("api.server.start_server.forward80to443.disabled_while_using_lets_encrypt"))
|
|
}
|
|
|
|
a.Srv.didFinishListen = make(chan struct{})
|
|
go func() {
|
|
var err error
|
|
if *a.Config().ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS {
|
|
if *a.Config().ServiceSettings.UseLetsEncrypt {
|
|
|
|
tlsConfig := &tls.Config{
|
|
GetCertificate: m.GetCertificate,
|
|
}
|
|
|
|
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")
|
|
|
|
a.Srv.Server.TLSConfig = tlsConfig
|
|
err = a.Srv.Server.ServeTLS(listener, "", "")
|
|
} else {
|
|
err = a.Srv.Server.ServeTLS(listener, *a.Config().ServiceSettings.TLSCertFile, *a.Config().ServiceSettings.TLSKeyFile)
|
|
}
|
|
} else {
|
|
err = a.Srv.Server.Serve(listener)
|
|
}
|
|
if err != nil && err != http.ErrServerClosed {
|
|
mlog.Critical(fmt.Sprintf("Error starting server, err:%v", err))
|
|
time.Sleep(time.Second)
|
|
}
|
|
close(a.Srv.didFinishListen)
|
|
}()
|
|
|
|
return 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 {
|
|
mlog.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
|
|
}
|
|
}
|
|
|
|
func (a *App) OriginChecker() func(*http.Request) bool {
|
|
if allowed := *a.Config().ServiceSettings.AllowCorsFrom; allowed != "" {
|
|
return utils.OriginChecker(allowed)
|
|
}
|
|
return 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()
|
|
}
|
|
}
|