package server import ( "context" "os" "os/signal" "sync" "syscall" "time" fiberzap "github.com/gofiber/contrib/fiberzap/v2" "github.com/gofiber/contrib/swagger" "github.com/gofiber/fiber/v2" middleware "github.com/oapi-codegen/fiber-middleware" "gitlab.mareshq.com/hq/backoffice/backoffice-api/internal/api" "go.uber.org/zap" ) type Server struct { ready ready } func NewServer() *Server { return &Server{} } func (s *Server) Run() { shutdownCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() logger, err := zap.NewProduction() if err != nil { logger.Fatal("failed to initialize logger", zap.Error(err)) } defer logger.Sync() sugaredLogger := logger.Sugar() fiberConfig := fiber.Config{ ReadTimeout: 5 * time.Second, WriteTimeout: 5 * time.Second, IdleTimeout: 5 * time.Second, DisableStartupMessage: true, } app := fiber.New(fiberConfig) app.Get("/", func(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusNotFound) }) app.Get("/livez", func(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) }) app.Get("/readyz", func(c *fiber.Ctx) error { if s.ready.isReady() { return c.SendStatus(fiber.StatusOK) } return c.SendStatus(fiber.StatusServiceUnavailable) }) // TODO: add /metrics endpoint app.Use(fiberzap.New(fiberzap.Config{ Logger: logger, })) swaggerConfig := swagger.Config{ BasePath: "/", FilePath: "./api/v1/openapi.yaml", Path: "/swagger", Title: "Swagger API Docs", } app.Use(swagger.New(swaggerConfig)) app = registerAPIHandlers(app, sugaredLogger) <-shutdownCtx.Done() stop() s.ready.notReady() // wait till Pod is signaling not ready (no new requests) time.Sleep(5 * time.Second) shutdownBegan := time.Now() sugaredLogger.Info("Shutdown signal recieved. Shutting down gracefully...") // * Gracefully shut down fiber server // * If does not shutdown within `gracefulShutdownPeriod`, force shutdown // ! Does not close keep-alive connections // ! fiber.Config{ReadTimeout} must be set and non-zero and greater than the `gracefulShutdownPeriod` // TODO: make shutdown timeout configurable err = app.ShutdownWithTimeout(time.Second * 10) if err != nil { sugaredLogger.Errorw("Error during shutdown", "error", err) } sugaredLogger.Infof("Shutdown completed in %v", time.Since(shutdownBegan)) } func registerAPIHandlers(fiberApp *fiber.App, logger *zap.SugaredLogger) *fiber.App { swagger, err := api.GetSwagger() if err != nil { logger.Fatalf("Error getting swagger: %v", err) } // TODO: validate this. Copied from example // See: https://github.com/deepmap/oapi-codegen/blob/master/examples/petstore-expanded/fiber/petstore.go#L41 // Clear out the servers array in the swagger spec, that skips validating // that server names match. We don't know how this thing will be run. swagger.Servers = nil fiberApp.Use(middleware.OapiRequestValidator(swagger)) api.RegisterHandlers(fiberApp) return fiberApp } type ready struct { ready bool lock sync.Mutex } func (r *ready) setReady() { r.lock.Lock() defer r.lock.Unlock() r.ready = true } func (r *ready) isReady() bool { r.lock.Lock() defer r.lock.Unlock() return r.ready } func (r *ready) notReady() { r.lock.Lock() defer r.lock.Unlock() r.ready = false }