docs(httpserver): correct tier from 4 to 3
httpserver depends on launcher (Tier 2), placing it at Tier 3. With launcher corrected from Tier 5 to Tier 2, httpserver's tier drops accordingly.
This commit is contained in:
116
httpserver.go
Normal file
116
httpserver.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"code.nochebuena.dev/go/launcher"
|
||||
)
|
||||
|
||||
// Logger is the minimal interface httpserver needs — satisfied by logz.Logger.
|
||||
type Logger interface {
|
||||
Info(msg string, args ...any)
|
||||
Error(msg string, err error, args ...any)
|
||||
}
|
||||
|
||||
// Config holds the HTTP server configuration.
|
||||
type Config struct {
|
||||
Host string `env:"SERVER_HOST" envDefault:"0.0.0.0"`
|
||||
Port int `env:"SERVER_PORT" envDefault:"8080"`
|
||||
ReadTimeout time.Duration `env:"SERVER_READ_TIMEOUT" envDefault:"5s"`
|
||||
WriteTimeout time.Duration `env:"SERVER_WRITE_TIMEOUT" envDefault:"10s"`
|
||||
IdleTimeout time.Duration `env:"SERVER_IDLE_TIMEOUT" envDefault:"120s"`
|
||||
}
|
||||
|
||||
// serverOpts holds functional options.
|
||||
type serverOpts struct {
|
||||
middleware []func(http.Handler) http.Handler
|
||||
}
|
||||
|
||||
// Option configures an httpserver component.
|
||||
type Option func(*serverOpts)
|
||||
|
||||
// WithMiddleware applies one or more middleware to the root chi router during OnInit.
|
||||
func WithMiddleware(mw ...func(http.Handler) http.Handler) Option {
|
||||
return func(o *serverOpts) {
|
||||
o.middleware = append(o.middleware, mw...)
|
||||
}
|
||||
}
|
||||
|
||||
// HttpServerComponent is a launcher.Component that also exposes a chi.Router.
|
||||
// Embedding chi.Router gives callers the full routing API: Get, Post, Route, Mount, Use, etc.
|
||||
type HttpServerComponent interface {
|
||||
launcher.Component
|
||||
chi.Router
|
||||
}
|
||||
|
||||
type httpServer struct {
|
||||
chi.Router
|
||||
logger Logger
|
||||
cfg Config
|
||||
opts serverOpts
|
||||
srv *http.Server
|
||||
}
|
||||
|
||||
// New creates an HttpServerComponent. No middleware is installed by default;
|
||||
// use [WithMiddleware] to compose the middleware stack.
|
||||
func New(logger Logger, cfg Config, opts ...Option) HttpServerComponent {
|
||||
o := serverOpts{}
|
||||
for _, opt := range opts {
|
||||
opt(&o)
|
||||
}
|
||||
return &httpServer{
|
||||
Router: chi.NewRouter(),
|
||||
logger: logger,
|
||||
cfg: cfg,
|
||||
opts: o,
|
||||
}
|
||||
}
|
||||
|
||||
// OnInit applies registered middleware to the router. No-op if none provided.
|
||||
func (s *httpServer) OnInit() error {
|
||||
for _, mw := range s.opts.middleware {
|
||||
s.Router.Use(mw)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnStart starts the HTTP server in a background goroutine.
|
||||
func (s *httpServer) OnStart() error {
|
||||
host := s.cfg.Host
|
||||
if host == "" {
|
||||
host = "0.0.0.0"
|
||||
}
|
||||
port := s.cfg.Port
|
||||
if port == 0 {
|
||||
port = 8080
|
||||
}
|
||||
s.srv = &http.Server{
|
||||
Addr: fmt.Sprintf("%s:%d", host, port),
|
||||
Handler: s.Router,
|
||||
ReadTimeout: s.cfg.ReadTimeout,
|
||||
WriteTimeout: s.cfg.WriteTimeout,
|
||||
IdleTimeout: s.cfg.IdleTimeout,
|
||||
}
|
||||
s.logger.Info("httpserver: starting", "addr", s.srv.Addr)
|
||||
go func() {
|
||||
if err := s.srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
s.logger.Error("httpserver: fatal error", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnStop performs a graceful shutdown, waiting up to 10 seconds for in-flight
|
||||
// requests to complete.
|
||||
func (s *httpServer) OnStop() error {
|
||||
s.logger.Info("httpserver: shutting down gracefully")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
return s.srv.Shutdown(ctx)
|
||||
}
|
||||
Reference in New Issue
Block a user