server.go 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. // Package httpserver is the shared HTTP scaffolding used by every
  2. // service. It wires /health and /metrics and applies a graceful
  3. // shutdown to whatever handlers the caller passes in.
  4. package httpserver
  5. import (
  6. "context"
  7. "errors"
  8. "log/slog"
  9. "net/http"
  10. "time"
  11. "git3.techno-world.net/lrosales/broad-announce/internal/observability"
  12. )
  13. // Server bundles the http.Server with its config and the prom registry
  14. // the caller wants to expose on /metrics.
  15. type Server struct {
  16. cfg Config
  17. srv *http.Server
  18. logger *slog.Logger
  19. registry http.Handler
  20. }
  21. // Config is the bits the caller can change.
  22. type Config struct {
  23. Addr string
  24. ServiceName string
  25. ShutdownGrace time.Duration
  26. ReadTimeout time.Duration
  27. WriteTimeout time.Duration
  28. IdleTimeout time.Duration
  29. MaxHeaderBytes int
  30. }
  31. // New constructs a Server with the given ServiceName and a custom mux
  32. // (the caller is expected to add /v1/* handlers; /health and /metrics
  33. // are added automatically).
  34. func New(cfg Config, logger *slog.Logger, reg http.Handler) *Server {
  35. if cfg.ReadTimeout == 0 {
  36. cfg.ReadTimeout = 10 * time.Second
  37. }
  38. if cfg.WriteTimeout == 0 {
  39. cfg.WriteTimeout = 30 * time.Second
  40. }
  41. if cfg.IdleTimeout == 0 {
  42. cfg.IdleTimeout = 60 * time.Second
  43. }
  44. if cfg.MaxHeaderBytes == 0 {
  45. cfg.MaxHeaderBytes = 1 << 20 // 1 MB
  46. }
  47. mux := http.NewServeMux()
  48. mux.Handle("/health", observability.HealthHandler(cfg.ServiceName))
  49. mux.Handle("/metrics", reg)
  50. return &Server{
  51. cfg: cfg,
  52. logger: logger,
  53. registry: reg,
  54. srv: &http.Server{
  55. Addr: cfg.Addr,
  56. Handler: mux,
  57. ReadTimeout: cfg.ReadTimeout,
  58. WriteTimeout: cfg.WriteTimeout,
  59. IdleTimeout: cfg.IdleTimeout,
  60. MaxHeaderBytes: cfg.MaxHeaderBytes,
  61. },
  62. }
  63. }
  64. // Mux returns the underlying mux so the caller can add /v1/* routes.
  65. func (s *Server) Mux() *http.ServeMux {
  66. return s.srv.Handler.(*http.ServeMux)
  67. }
  68. // Start runs ListenAndServe. Returns when the server stops.
  69. func (s *Server) Start() error {
  70. s.logger.Info("http listening", "addr", s.cfg.Addr)
  71. if err := s.srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
  72. return err
  73. }
  74. return nil
  75. }
  76. // Shutdown gracefully stops the server.
  77. func (s *Server) Shutdown(ctx context.Context) error {
  78. ctx, cancel := context.WithTimeout(ctx, s.cfg.ShutdownGrace)
  79. defer cancel()
  80. return s.srv.Shutdown(ctx)
  81. }