package serve import ( "context" "errors" "fmt" "net/http" "os" "os/signal" "strings" "syscall" "time" "gitea.dwysokinski.me/Kichiyaki/chizap" "gitea.dwysokinski.me/twhelp/sessions/cmd/sessions/internal" "gitea.dwysokinski.me/twhelp/sessions/internal/bundb" "gitea.dwysokinski.me/twhelp/sessions/internal/router/meta" "github.com/uptrace/bun" "github.com/urfave/cli/v2" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "go.uber.org/zap" ) const ( defaultPort = "9234" readTimeout = 2 * time.Second readHeaderTimeout = 2 * time.Second writeTimeout = 2 * time.Second idleTimeout = 2 * time.Second serverShutdownTimeout = 10 * time.Second metaEndpointsPrefix = "/_meta" ) func New() *cli.Command { return &cli.Command{ Name: "serve", Usage: "Runs the http server", Action: func(c *cli.Context) error { db, err := internal.NewBunDB() if err != nil { return fmt.Errorf("internal.NewBunDB: %w", err) } defer func() { _ = db.Close() }() logger := zap.L() srv, err := newServer(serverConfig{ appVersion: c.App.Version, logger: logger, db: db, }) if err != nil { return fmt.Errorf("newServer: %w", err) } go runServer(srv, logger) logger.Info("Server is listening on the port "+defaultPort, zap.String("port", defaultPort)) waitForSignal(c.Context) ctxShutdown, cancelShutdown := context.WithTimeout(c.Context, serverShutdownTimeout) defer cancelShutdown() if err := srv.Shutdown(ctxShutdown); err != nil { return fmt.Errorf("srv.Shutdown: %w", err) } return nil }, } } type serverConfig struct { appVersion string logger *zap.Logger db *bun.DB } func newServer(cfg serverConfig) (*http.Server, error) { // router r := chi.NewRouter() r.Use(getChiMiddlewares(cfg.logger)...) r.Mount(metaEndpointsPrefix, meta.NewRouter([]meta.Checker{bundb.NewChecker(cfg.db)})) return &http.Server{ Addr: ":" + defaultPort, Handler: r, ReadTimeout: readTimeout, ReadHeaderTimeout: readHeaderTimeout, WriteTimeout: writeTimeout, IdleTimeout: idleTimeout, }, nil } func getChiMiddlewares(logger *zap.Logger) chi.Middlewares { return chi.Middlewares{ middleware.RealIP, chizap.Logger(logger, chizap.WithFilter(omitMetaEndpoints)), middleware.Recoverer, } } func runServer(srv *http.Server, logger *zap.Logger) { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { logger.Fatal("srv.ListenAndServe:" + err.Error()) } } func waitForSignal(ctx context.Context) { ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) defer stop() <-ctx.Done() } func omitMetaEndpoints(r *http.Request) bool { return !strings.HasPrefix(r.URL.Path, metaEndpointsPrefix) }