github.com/jlevesy/mattermost-server@v5.3.2-0.20181003190404-7468f35cb0c8+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  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"net"
    13  	"net/http"
    14  	"net/url"
    15  	"os"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/gorilla/handlers"
    20  	"github.com/gorilla/mux"
    21  	"github.com/pkg/errors"
    22  	"github.com/rs/cors"
    23  	"golang.org/x/crypto/acme/autocert"
    24  
    25  	"github.com/mattermost/mattermost-server/mlog"
    26  	"github.com/mattermost/mattermost-server/model"
    27  	"github.com/mattermost/mattermost-server/store"
    28  	"github.com/mattermost/mattermost-server/utils"
    29  )
    30  
    31  type Server struct {
    32  	Store           store.Store
    33  	WebSocketRouter *WebSocketRouter
    34  
    35  	// RootRouter is the starting point for all HTTP requests to the server.
    36  	RootRouter *mux.Router
    37  
    38  	// Router is the starting point for all web, api4 and ws requests to the server. It differs
    39  	// from RootRouter only if the SiteURL contains a /subpath.
    40  	Router *mux.Router
    41  
    42  	Server      *http.Server
    43  	ListenAddr  *net.TCPAddr
    44  	RateLimiter *RateLimiter
    45  
    46  	didFinishListen chan struct{}
    47  }
    48  
    49  var corsAllowedMethods []string = []string{
    50  	"POST",
    51  	"GET",
    52  	"OPTIONS",
    53  	"PUT",
    54  	"PATCH",
    55  	"DELETE",
    56  }
    57  
    58  type RecoveryLogger struct {
    59  }
    60  
    61  func (rl *RecoveryLogger) Println(i ...interface{}) {
    62  	mlog.Error("Please check the std error output for the stack trace")
    63  	mlog.Error(fmt.Sprint(i...))
    64  }
    65  
    66  const TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN = time.Second
    67  
    68  // golang.org/x/crypto/acme/autocert/autocert.go
    69  func handleHTTPRedirect(w http.ResponseWriter, r *http.Request) {
    70  	if r.Method != "GET" && r.Method != "HEAD" {
    71  		http.Error(w, "Use HTTPS", http.StatusBadRequest)
    72  		return
    73  	}
    74  	target := "https://" + stripPort(r.Host) + r.URL.RequestURI()
    75  	http.Redirect(w, r, target, http.StatusFound)
    76  }
    77  
    78  // golang.org/x/crypto/acme/autocert/autocert.go
    79  func stripPort(hostport string) string {
    80  	host, _, err := net.SplitHostPort(hostport)
    81  	if err != nil {
    82  		return hostport
    83  	}
    84  	return net.JoinHostPort(host, "443")
    85  }
    86  
    87  func (a *App) StartServer() error {
    88  	mlog.Info("Starting Server...")
    89  
    90  	var handler http.Handler = a.Srv.RootRouter
    91  	if allowedOrigins := *a.Config().ServiceSettings.AllowCorsFrom; allowedOrigins != "" {
    92  		exposedCorsHeaders := *a.Config().ServiceSettings.CorsExposedHeaders
    93  		allowCredentials := *a.Config().ServiceSettings.CorsAllowCredentials
    94  		debug := *a.Config().ServiceSettings.CorsDebug
    95  		corsWrapper := cors.New(cors.Options{
    96  			AllowedOrigins:   strings.Fields(allowedOrigins),
    97  			AllowedMethods:   corsAllowedMethods,
    98  			AllowedHeaders:   []string{"*"},
    99  			ExposedHeaders:   strings.Fields(exposedCorsHeaders),
   100  			MaxAge:           86400,
   101  			AllowCredentials: allowCredentials,
   102  			Debug:            debug,
   103  		})
   104  
   105  		// If we have debugging of CORS turned on then forward messages to logs
   106  		if debug {
   107  			corsWrapper.Log = a.Log.StdLog(mlog.String("source", "cors"))
   108  		}
   109  
   110  		handler = corsWrapper.Handler(handler)
   111  	}
   112  
   113  	if *a.Config().RateLimitSettings.Enable {
   114  		mlog.Info("RateLimiter is enabled")
   115  
   116  		rateLimiter, err := NewRateLimiter(&a.Config().RateLimitSettings)
   117  		if err != nil {
   118  			return err
   119  		}
   120  
   121  		a.Srv.RateLimiter = rateLimiter
   122  		handler = rateLimiter.RateLimitHandler(handler)
   123  	}
   124  
   125  	a.Srv.Server = &http.Server{
   126  		Handler:      handlers.RecoveryHandler(handlers.RecoveryLogger(&RecoveryLogger{}), handlers.PrintRecoveryStack(true))(handler),
   127  		ReadTimeout:  time.Duration(*a.Config().ServiceSettings.ReadTimeout) * time.Second,
   128  		WriteTimeout: time.Duration(*a.Config().ServiceSettings.WriteTimeout) * time.Second,
   129  		ErrorLog:     a.Log.StdLog(mlog.String("source", "httpserver")),
   130  	}
   131  
   132  	addr := *a.Config().ServiceSettings.ListenAddress
   133  	if addr == "" {
   134  		if *a.Config().ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS {
   135  			addr = ":https"
   136  		} else {
   137  			addr = ":http"
   138  		}
   139  	}
   140  
   141  	listener, err := net.Listen("tcp", addr)
   142  	if err != nil {
   143  		errors.Wrapf(err, utils.T("api.server.start_server.starting.critical"), err)
   144  		return err
   145  	}
   146  	a.Srv.ListenAddr = listener.Addr().(*net.TCPAddr)
   147  
   148  	mlog.Info(fmt.Sprintf("Server is listening on %v", listener.Addr().String()))
   149  
   150  	// Migration from old let's encrypt library
   151  	if *a.Config().ServiceSettings.UseLetsEncrypt {
   152  		if stat, err := os.Stat(*a.Config().ServiceSettings.LetsEncryptCertificateCacheFile); err == nil && !stat.IsDir() {
   153  			os.Remove(*a.Config().ServiceSettings.LetsEncryptCertificateCacheFile)
   154  		}
   155  	}
   156  
   157  	m := &autocert.Manager{
   158  		Cache:  autocert.DirCache(*a.Config().ServiceSettings.LetsEncryptCertificateCacheFile),
   159  		Prompt: autocert.AcceptTOS,
   160  	}
   161  
   162  	if *a.Config().ServiceSettings.Forward80To443 {
   163  		if host, port, err := net.SplitHostPort(addr); err != nil {
   164  			mlog.Error("Unable to setup forwarding: " + err.Error())
   165  		} else if port != "443" {
   166  			return fmt.Errorf(utils.T("api.server.start_server.forward80to443.enabled_but_listening_on_wrong_port"), port)
   167  		} else {
   168  			httpListenAddress := net.JoinHostPort(host, "http")
   169  
   170  			if *a.Config().ServiceSettings.UseLetsEncrypt {
   171  				server := &http.Server{
   172  					Addr:     httpListenAddress,
   173  					Handler:  m.HTTPHandler(nil),
   174  					ErrorLog: a.Log.StdLog(mlog.String("source", "le_forwarder_server")),
   175  				}
   176  				go server.ListenAndServe()
   177  			} else {
   178  				go func() {
   179  					redirectListener, err := net.Listen("tcp", httpListenAddress)
   180  					if err != nil {
   181  						mlog.Error("Unable to setup forwarding: " + err.Error())
   182  						return
   183  					}
   184  					defer redirectListener.Close()
   185  
   186  					server := &http.Server{
   187  						Handler:  http.HandlerFunc(handleHTTPRedirect),
   188  						ErrorLog: a.Log.StdLog(mlog.String("source", "forwarder_server")),
   189  					}
   190  					server.Serve(redirectListener)
   191  				}()
   192  			}
   193  		}
   194  	} else if *a.Config().ServiceSettings.UseLetsEncrypt {
   195  		return errors.New(utils.T("api.server.start_server.forward80to443.disabled_while_using_lets_encrypt"))
   196  	}
   197  
   198  	a.Srv.didFinishListen = make(chan struct{})
   199  	go func() {
   200  		var err error
   201  		if *a.Config().ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS {
   202  			if *a.Config().ServiceSettings.UseLetsEncrypt {
   203  
   204  				tlsConfig := &tls.Config{
   205  					GetCertificate: m.GetCertificate,
   206  				}
   207  
   208  				tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")
   209  
   210  				a.Srv.Server.TLSConfig = tlsConfig
   211  				err = a.Srv.Server.ServeTLS(listener, "", "")
   212  			} else {
   213  				err = a.Srv.Server.ServeTLS(listener, *a.Config().ServiceSettings.TLSCertFile, *a.Config().ServiceSettings.TLSKeyFile)
   214  			}
   215  		} else {
   216  			err = a.Srv.Server.Serve(listener)
   217  		}
   218  		if err != nil && err != http.ErrServerClosed {
   219  			mlog.Critical(fmt.Sprintf("Error starting server, err:%v", err))
   220  			time.Sleep(time.Second)
   221  		}
   222  		close(a.Srv.didFinishListen)
   223  	}()
   224  
   225  	return nil
   226  }
   227  
   228  func (a *App) StopServer() {
   229  	if a.Srv.Server != nil {
   230  		ctx, cancel := context.WithTimeout(context.Background(), TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN)
   231  		defer cancel()
   232  		didShutdown := false
   233  		for a.Srv.didFinishListen != nil && !didShutdown {
   234  			if err := a.Srv.Server.Shutdown(ctx); err != nil {
   235  				mlog.Warn(err.Error())
   236  			}
   237  			timer := time.NewTimer(time.Millisecond * 50)
   238  			select {
   239  			case <-a.Srv.didFinishListen:
   240  				didShutdown = true
   241  			case <-timer.C:
   242  			}
   243  			timer.Stop()
   244  		}
   245  		a.Srv.Server.Close()
   246  		a.Srv.Server = nil
   247  	}
   248  }
   249  
   250  func (a *App) OriginChecker() func(*http.Request) bool {
   251  	if allowed := *a.Config().ServiceSettings.AllowCorsFrom; allowed != "" {
   252  		if allowed != "*" {
   253  			siteURL, err := url.Parse(*a.Config().ServiceSettings.SiteURL)
   254  			if err == nil {
   255  				siteURL.Path = ""
   256  				allowed += " " + siteURL.String()
   257  			}
   258  		}
   259  
   260  		return utils.OriginChecker(allowed)
   261  	}
   262  	return nil
   263  }
   264  
   265  // This is required to re-use the underlying connection and not take up file descriptors
   266  func consumeAndClose(r *http.Response) {
   267  	if r.Body != nil {
   268  		io.Copy(ioutil.Discard, r.Body)
   269  		r.Body.Close()
   270  	}
   271  }