| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889 |
- // Package httpserver is the shared HTTP scaffolding used by every
- // service. It wires /health and /metrics and applies a graceful
- // shutdown to whatever handlers the caller passes in.
- package httpserver
- import (
- "context"
- "errors"
- "log/slog"
- "net/http"
- "time"
- "git3.techno-world.net/lrosales/broad-announce/internal/observability"
- )
- // Server bundles the http.Server with its config and the prom registry
- // the caller wants to expose on /metrics.
- type Server struct {
- cfg Config
- srv *http.Server
- logger *slog.Logger
- registry http.Handler
- }
- // Config is the bits the caller can change.
- type Config struct {
- Addr string
- ServiceName string
- ShutdownGrace time.Duration
- ReadTimeout time.Duration
- WriteTimeout time.Duration
- IdleTimeout time.Duration
- MaxHeaderBytes int
- }
- // New constructs a Server with the given ServiceName and a custom mux
- // (the caller is expected to add /v1/* handlers; /health and /metrics
- // are added automatically).
- func New(cfg Config, logger *slog.Logger, reg http.Handler) *Server {
- if cfg.ReadTimeout == 0 {
- cfg.ReadTimeout = 10 * time.Second
- }
- if cfg.WriteTimeout == 0 {
- cfg.WriteTimeout = 30 * time.Second
- }
- if cfg.IdleTimeout == 0 {
- cfg.IdleTimeout = 60 * time.Second
- }
- if cfg.MaxHeaderBytes == 0 {
- cfg.MaxHeaderBytes = 1 << 20 // 1 MB
- }
- mux := http.NewServeMux()
- mux.Handle("/health", observability.HealthHandler(cfg.ServiceName))
- mux.Handle("/metrics", reg)
- return &Server{
- cfg: cfg,
- logger: logger,
- registry: reg,
- srv: &http.Server{
- Addr: cfg.Addr,
- Handler: mux,
- ReadTimeout: cfg.ReadTimeout,
- WriteTimeout: cfg.WriteTimeout,
- IdleTimeout: cfg.IdleTimeout,
- MaxHeaderBytes: cfg.MaxHeaderBytes,
- },
- }
- }
- // Mux returns the underlying mux so the caller can add /v1/* routes.
- func (s *Server) Mux() *http.ServeMux {
- return s.srv.Handler.(*http.ServeMux)
- }
- // Start runs ListenAndServe. Returns when the server stops.
- func (s *Server) Start() error {
- s.logger.Info("http listening", "addr", s.cfg.Addr)
- if err := s.srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
- return err
- }
- return nil
- }
- // Shutdown gracefully stops the server.
- func (s *Server) Shutdown(ctx context.Context) error {
- ctx, cancel := context.WithTimeout(ctx, s.cfg.ShutdownGrace)
- defer cancel()
- return s.srv.Shutdown(ctx)
- }
|