145 lines
3.2 KiB
Go
145 lines
3.2 KiB
Go
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
|
|
}
|