# httpserver Lifecycle-managed HTTP server built on chi, implementing `launcher.Component` and embedding `chi.Router`. ## Purpose Provides an HTTP server that integrates with the `launcher` lifecycle (OnInit → OnStart → OnStop) while exposing the full chi routing API directly. Callers use a single value for both lifecycle management and route registration. ## Tier & Dependencies **Tier 3** (transport layer). Depends on: - `code.nochebuena.dev/go/launcher` — Component interface - `github.com/go-chi/chi/v5` — router (part of the public API) No application-level or business-logic dependencies. Do not import domain, service, or repository packages from this module. ## Key Design Decisions - **chi over Echo** (ADR-001): chi uses stdlib `http.Handler` everywhere. Every middleware in the micro-lib stack (`httpmw`) and every handler adapter (`httputil`) uses `http.Handler`/`http.ResponseWriter`/`*http.Request`. Echo's custom context type would require wrapper code at every boundary. - **Embedded `chi.Router`** (ADR-002): `HttpServerComponent` embeds `chi.Router` directly, so callers get the full routing API (`Get`, `Post`, `Route`, `Mount`, `Use`, `With`, `Group`, etc.) without a wrapper. The concrete `httpServer` struct embeds `chi.NewRouter()`. - **No default middleware** (ADR-003): `New()` installs nothing. Middleware is composed explicitly with `WithMiddleware(...)`. Order is caller-controlled and visible in the application source. - **Duck-typed Logger** (global ADR-001): The `Logger` interface requires only `Info(msg string, args ...any)` and `Error(msg string, err error, args ...any)`. Any struct with those two methods satisfies it — including `logz.Logger` and application-local adapters. - **Graceful shutdown**: `OnStop()` calls `http.Server.Shutdown` with a 10-second timeout derived from `context.WithTimeout(context.Background(), 10*time.Second)`. In-flight requests have up to 10 seconds to complete before the server returns. ## Patterns **Construction and wiring:** ```go srv := httpserver.New(logger, httpserver.Config{Port: 3000}, httpserver.WithMiddleware( httpmw.RequestID(uuid.NewString), httpmw.Recover(), httpmw.RequestLogger(logger), ), ) lc.Append(db, srv) ``` **Route registration in BeforeStart (after db init, before port bind):** ```go lc.BeforeStart(func() error { srv.Get("/health", healthHandler) srv.Route("/api/v1", func(r chi.Router) { r.Get("/todos", todoHandler.FindAll) r.Post("/todos", todoHandler.Create) }) return nil }) ``` **Middleware applied to a sub-group:** ```go srv.Group(func(r chi.Router) { r.Use(appMW.Auth(userRepo)) r.With(appMW.Require(permProvider, resource, perm)).Get("/protected", handler) }) ``` **Config env vars:** | Variable | Default | Description | |---|---|---| | `SERVER_HOST` | `0.0.0.0` | Bind address | | `SERVER_PORT` | `8080` | Listen port | | `SERVER_READ_TIMEOUT` | `5s` | Read timeout | | `SERVER_WRITE_TIMEOUT` | `10s` | Write timeout | | `SERVER_IDLE_TIMEOUT` | `120s` | Keep-alive idle timeout | ## What to Avoid - Do not call `srv.Use(mw)` directly after `OnInit()` has run — middleware registered after `OnInit` may not behave as expected depending on chi's internal state. Register all middleware via `WithMiddleware(...)` at construction time. - Do not register routes outside of a `BeforeStart` hook when using the launcher. Routes should be registered after dependent components (e.g., the database) have initialized. - Do not add a `chi`-incompatible router behind this interface. The `chi.Router` embed is part of the public contract. - Do not pass `nil` as the `Logger`. The logger is called on every start, stop, and fatal error. - Do not use Echo, Gin, or any framework that wraps `http.Handler` — it will be incompatible with `httpmw` and `httputil`. ## Testing Notes - `httpserver_test.go` runs as a white-box test (`package httpserver`) and uses `httptest.NewRequest` / `httptest.NewRecorder` to test routes without binding a port. - `compliance_test.go` is a black-box test (`package httpserver_test`) containing only compile-time assertions that `httpserver.New(...)` satisfies both `launcher.Component` and `chi.Router`. - Tests that need a real TCP listener use `freePort(t)` to find an available port and call `OnStart()` / `OnStop()` directly. - The `Logger` interface is minimal enough that a two-method struct in the test file satisfies it without any imports from logz.