package runtime import ( "fmt" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "net" "net/http" "sorbet/internal/util" "sorbet/pkg/env" "sorbet/pkg/rsp" "time" ) var ( // maxHeaderBytes is used by the http server to limit the size of request headers. // This may need to be increased if accepting cookies from the public. maxHeaderBytes = 1 << 20 // readTimeout is used by the http server to set a maximum duration before // timing out read of the request. The default timeout is 10 seconds. readTimeout = 10 * time.Second // writeTimeout is used by the http server to set a maximum duration before // timing out write of the response. The default timeout is 10 seconds. writeTimeout = 10 * time.Second // idleTimeout is used by the http server to set a maximum duration for // keep-alive connections. idleTimeout = 120 * time.Second ) func newEchoFramework() (*echo.Echo, error) { e := echo.New() e.HideBanner = true e.HidePort = true e.HTTPErrorHandler = rsp.HTTPErrorHandler e.Debug = !env.IsEnv("prod") e.Logger = util.NewEchoLogger() e.Use(middleware.Recover()) e.Use(middleware.CORS()) e.Use(middleware.Logger()) for _, servlet := range servlets { group := e.Group("") for _, routable := range servlet.Routes() { routable.InitRoutes(group) } } routes := e.Routes() e.GET("/_routes", func(c echo.Context) error { return c.JSON(http.StatusOK, routes) }) return e, nil } func createServer() (*http.Server, net.Listener, error) { e, err := newEchoFramework() if err != nil { return nil, nil, err } l, err := createTCPListener() if err != nil { return nil, nil, err } s := &http.Server{ Handler: e, MaxHeaderBytes: env.Int("SERVER_MAX_HEADER_BYTES", maxHeaderBytes), ReadTimeout: env.Duration("SERVER_READ_TIMEOUT", readTimeout), WriteTimeout: env.Duration("SERVER_WRITE_TIMEOUT", writeTimeout), IdleTimeout: env.Duration("SERVER_IDLE_TIMEOUT", idleTimeout), } return s, l, nil } func createTCPListener() (net.Listener, error) { l, err := net.Listen( env.String("SERVER_NETWORK", "tcp"), fmt.Sprintf("%s:%d", env.String("SERVER_ADDRESS", "0.0.0.0"), env.Int("SERVER_PORT", 1324), ), ) if err == nil { l = net.Listener(TCPKeepAliveListener{ TCPListener: l.(*net.TCPListener), }) } return l, err } // TCPKeepAliveListener sets TCP keep-alive timeouts on accepted // connections. It's used by ListenAndServe and ListenAndServeTLS so // dead TCP connections (e.g. closing laptop mid-download) eventually // go away. // // This is here because it is not exposed in the stdlib and // we'd prefer to have a hold of the http.Server's net.Listener so we can close it // on shutdown. // // Taken from here: https://golang.org/src/net/http/server.go?s=63121:63175#L2120 type TCPKeepAliveListener struct { *net.TCPListener } // Accept accepts the next incoming call and returns the new // connection. KeepAlivePeriod is set properly. func (ln TCPKeepAliveListener) Accept() (c net.Conn, err error) { tc, err := ln.AcceptTCP() if err != nil { return } err = tc.SetKeepAlive(true) if err != nil { return } err = tc.SetKeepAlivePeriod(3 * time.Minute) if err != nil { return } return tc, nil }