// Package adminapi exposes the optional private admin HTTP listener used for // operational endpoints such as Prometheus metrics. package adminapi import ( "context" "errors" "fmt" "net" "net/http" "sync" "galaxy/gateway/internal/config" "go.uber.org/zap" ) // Server owns the optional admin HTTP listener exposed by the gateway. type Server struct { cfg config.AdminHTTPConfig handler http.Handler logger *zap.Logger stateMu sync.RWMutex server *http.Server listener net.Listener } // NewServer constructs an admin HTTP server for cfg and handler. func NewServer(cfg config.AdminHTTPConfig, handler http.Handler, logger *zap.Logger) *Server { if handler == nil { handler = http.NotFoundHandler() } if logger == nil { logger = zap.NewNop() } return &Server{ cfg: cfg, handler: handler, logger: logger.Named("admin_http"), } } // Enabled reports whether the admin listener should run. func (s *Server) Enabled() bool { return s != nil && s.cfg.Addr != "" } // Run binds the configured listener and serves the admin HTTP surface until // Shutdown closes the server. A disabled admin server returns when ctx is // canceled. func (s *Server) Run(ctx context.Context) error { if ctx == nil { return errors.New("run admin HTTP server: nil context") } if err := ctx.Err(); err != nil { return err } if !s.Enabled() { <-ctx.Done() return nil } listener, err := net.Listen("tcp", s.cfg.Addr) if err != nil { return fmt.Errorf("run admin HTTP server: listen on %q: %w", s.cfg.Addr, err) } server := &http.Server{ Handler: s.handler, ReadHeaderTimeout: s.cfg.ReadHeaderTimeout, ReadTimeout: s.cfg.ReadTimeout, IdleTimeout: s.cfg.IdleTimeout, } s.stateMu.Lock() s.server = server s.listener = listener s.stateMu.Unlock() s.logger.Info("admin HTTP server started", zap.String("addr", listener.Addr().String())) defer func() { s.stateMu.Lock() s.server = nil s.listener = nil s.stateMu.Unlock() }() err = server.Serve(listener) switch { case err == nil: return nil case errors.Is(err, http.ErrServerClosed): s.logger.Info("admin HTTP server stopped") return nil default: return fmt.Errorf("run admin HTTP server: serve on %q: %w", s.cfg.Addr, err) } } // Shutdown gracefully stops the admin HTTP server within ctx. func (s *Server) Shutdown(ctx context.Context) error { if ctx == nil { return errors.New("shutdown admin HTTP server: nil context") } s.stateMu.RLock() server := s.server s.stateMu.RUnlock() if server == nil { return nil } if err := server.Shutdown(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) { return fmt.Errorf("shutdown admin HTTP server: %w", err) } return nil } func (s *Server) listenAddr() string { s.stateMu.RLock() defer s.stateMu.RUnlock() if s.listener == nil { return "" } return s.listener.Addr().String() }