package server import ( "context" "errors" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" oapimiddleware "github.com/oapi-codegen/nethttp-middleware" "gitlab.mareshq.com/hq/backoffice/backoffice-api/internal/training" "log" "net/http" "time" ) type Server struct { logger *log.Logger hostname string trainingRepository training.Repository router *chi.Mux srv *http.Server tls *TLS } type TLS struct { CertFile string KeyFile string } func NewServer(hostname string, logger *log.Logger, trainingRepository training.Repository) *Server { return &Server{ logger: logger, hostname: hostname, trainingRepository: trainingRepository, router: chi.NewRouter(), tls: nil, } } func NewServerWithTLS(hostname string, tls *TLS, logger *log.Logger, trainingRepository training.Repository) *Server { s := NewServer(hostname, logger, trainingRepository) s.tls = tls return s } func (s *Server) Run(ctx context.Context) error { swagger, err := GetSwagger() if err != nil { return err } // 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 s.router.Use(middleware.Logger) // middleware.Recoverer recovers from panics, logs the panic (and a stack trace), s.router.Use(middleware.Recoverer) // we trust headers, since we are running on Kubernetes with Ingress Controller (Ingress-NGINX) // and behind an L4 load balancer (on Hetzner Cloud) s.router.Use(middleware.RealIP) // middleware.RequestID generates a request ID and adds it to request context // if the request has an `X-Request-ID` header, it will use that as the request ID s.router.Use(middleware.RequestID) s.router.Use(middleware.Timeout(10 * time.Second)) s.router.Use(oapimiddleware.OapiRequestValidator(swagger)) // create handler h := NewStrictHandler(s, nil) // register endpoints HandlerFromMux(h, s.router) s.srv = &http.Server{ Addr: ":8080", // TODO: make port configurable Handler: s.router, } go func() { var err error if s.tls != nil { s.logger.Printf("Starting HTTPS server on: %s\n", s.srv.Addr) err = s.srv.ListenAndServeTLS(s.tls.CertFile, s.tls.KeyFile) } else { s.logger.Printf("Starting HTTP server on: %s\n", s.srv.Addr) err = s.srv.ListenAndServe() } // suppress the error if the server was closed gracefully if err != nil && !errors.Is(err, http.ErrServerClosed) { s.logger.Printf("error: %v\n", err) } }() // wait for the context to be done // context done means the server is shutting down <-ctx.Done() // TODO: make graceful shutdown period configurable timeout := 10 * time.Second timeoutCtx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() s.logger.Printf("Shutting down server in %s seconds\n", timeout.String()) err = s.srv.Shutdown(timeoutCtx) // suppress the error if the server was closed gracefully if err != nil && !errors.Is(err, http.ErrServerClosed) { return err } s.logger.Println("Server shutdown successfully") return nil }