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(Recover) // 配置跨域 e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ AllowOrigins: env.List("CORS_ALLOW_ORIGINS", []string{"*"}), AllowMethods: env.List("CORS_ALLOW_METHODS", []string{"GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"}), AllowCredentials: env.Bool("CORS_ALLOW_CREDENTIALS", false), AllowHeaders: env.List("CORS_ALLOW_HEADERS", []string{"X-TICKET"}), ExposeHeaders: env.List("CORS_EXPOSE_HEADERS"), MaxAge: env.Int("CORS_MAX_AGE", 0), })) // 配置日志 e.Use(Logger) // 配置静态服务 e.Use(middleware.StaticWithConfig(middleware.StaticConfig{ Root: env.String("STATIC_ROOT", "web"), Index: env.String("STATIC_INDEX", "index.html"), HTML5: env.Bool("STATIC_HTML5", false), Browse: env.Bool("STATIC_DIRECTORY_BROWSE", false), })) 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 }