package ops import ( "context" "errors" "net/http" "strings" "time" "github.com/go-chi/chi/v5" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/tech/sendico/pkg/api/routers" "github.com/tech/sendico/pkg/api/routers/health" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" "go.uber.org/zap" ) const ( defaultAddress = ":9420" readHeaderTimeout = 5 * time.Second defaultShutdownWindow = 5 * time.Second ) type httpServer struct { logger mlogger.Logger server *http.Server health routers.Health timeout time.Duration } func newHTTPServer(logger mlogger.Logger, cfg HTTPServerConfig) (HTTPServer, error) { if logger == nil { return nil, merrors.InvalidArgument("ops: logger is nil") } address := strings.TrimSpace(cfg.Address) if address == "" { address = defaultAddress } r := chi.NewRouter() r.Handle("/metrics", promhttp.Handler()) metricsLogger := logger.Named("ops") var healthRouter routers.Health hr, err := routers.NewHealthRouter(metricsLogger, r, "") if err != nil { metricsLogger.Warn("Failed to initialise health router", zap.Error(err)) } else { hr.SetStatus(health.SSStarting) healthRouter = hr } httpSrv := &http.Server{ Addr: address, Handler: r, ReadHeaderTimeout: readHeaderTimeout, } wrapper := &httpServer{ logger: metricsLogger, server: httpSrv, health: healthRouter, timeout: defaultShutdownWindow, } go func() { metricsLogger.Info("Prometheus endpoint listening", zap.String("address", address)) serveErr := httpSrv.ListenAndServe() if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) { metricsLogger.Error("Prometheus endpoint stopped unexpectedly", zap.Error(serveErr)) if healthRouter != nil { healthRouter.SetStatus(health.SSTerminating) } } }() return wrapper, nil } func (s *httpServer) SetStatus(status health.ServiceStatus) { if s == nil || s.health == nil { return } s.health.SetStatus(status) } func (s *httpServer) Close(ctx context.Context) { if s == nil { return } if s.health != nil { s.health.SetStatus(health.SSTerminating) s.health.Finish() s.health = nil } if s.server == nil { return } shutdownCtx := ctx if shutdownCtx == nil { shutdownCtx = context.Background() } if s.timeout > 0 { var cancel context.CancelFunc shutdownCtx, cancel = context.WithTimeout(shutdownCtx, s.timeout) defer cancel() } if err := s.server.Shutdown(shutdownCtx); err != nil && !errors.Is(err, http.ErrServerClosed) { s.logger.Warn("Failed to stop metrics server", zap.Error(err)) } else { s.logger.Info("Metrics server stopped") } }