Documentation
¶
Overview ¶
Package middleware provides composable net/http middleware for the request backbone: request IDs, structured logging, panic recovery, and per-request timeouts. Each middleware is a func(http.Handler) http.Handler so they can be composed with Chain.
Index ¶
- func CaptureRoutePattern(next http.Handler) http.Handler
- func ClientIP(ctx context.Context) string
- func IsHTTPS(ctx context.Context) bool
- func RequestID(next http.Handler) http.Handler
- func RequestIDFromContext(ctx context.Context) string
- func RequestScheme(ctx context.Context) string
- type Middleware
- func Chain(mws ...Middleware) Middleware
- func ForwardedHeaders(trusted []netip.Prefix) Middleware
- func Metrics(m *metrics.HTTPMetrics) Middleware
- func Recoverer(logger *slog.Logger) Middleware
- func RequestLogger(logger *slog.Logger) Middleware
- func SecurityHeaders(hsts string) Middleware
- func Timeout(d time.Duration) Middleware
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CaptureRoutePattern ¶
CaptureRoutePattern records the ServeMux pattern that matched the request into the holder placed in the context by Metrics. It must sit innermost in the chain (directly wrapping the mux) so it reads r.Pattern from the exact request value the mux mutated. The capture is deferred so a matched pattern is still recorded when the handler panics (Recoverer, further out, turns that into a 500 which Metrics then attributes to the right route). It is a no-op when no holder is present (metrics disabled).
func ClientIP ¶
ClientIP returns the resolved client IP — XFF-aware behind a trusted proxy, otherwise the direct peer — or "" when the middleware did not run.
func IsHTTPS ¶
IsHTTPS reports whether the effective request scheme is https. It is the signal used to gate Secure cookies (NES-51) and HSTS (NES-52).
func RequestID ¶
RequestID assigns each request a correlation id: it reuses a *valid* incoming X-Request-Id, otherwise it generates one. The id is stored in the request context and echoed on the response header.
func RequestIDFromContext ¶
RequestIDFromContext returns the request id stored by RequestID, or "" when none is present.
func RequestScheme ¶
RequestScheme returns the effective request scheme ("http" or "https") as resolved by ForwardedHeaders, or "" when the middleware did not run.
Types ¶
type Middleware ¶
Middleware wraps an http.Handler with additional behavior.
func Chain ¶
func Chain(mws ...Middleware) Middleware
Chain composes middleware into a single Middleware. The first argument is the outermost wrapper: Chain(a, b)(h) yields a(b(h)), so a runs first on the way in and last on the way out.
func ForwardedHeaders ¶
func ForwardedHeaders(trusted []netip.Prefix) Middleware
ForwardedHeaders resolves the effective request scheme and client IP from the X-Forwarded-Proto / X-Forwarded-For headers, but only when the immediate peer (r.RemoteAddr) falls inside one of the trusted proxy networks. From an untrusted peer the forwarded headers are ignored, so a direct client cannot spoof an https (secure) context by sending X-Forwarded-Proto: https. The resolved values are stored once on the request context; read them with RequestScheme, ClientIP, and IsHTTPS so every downstream consumer agrees.
func Metrics ¶
func Metrics(m *metrics.HTTPMetrics) Middleware
Metrics records the request count, latency, and in-flight gauge defined in the metrics package for every request. A nil m returns a passthrough middleware so the canonical chain can include Metrics unconditionally while tests (and the first-run setup server) run without a registry.
The route label is the matched ServeMux pattern, delivered by CaptureRoutePattern via a context holder (see routePatternHolder for why reading r.Pattern here is not enough). When used standalone without CaptureRoutePattern, r.Pattern is used as a fallback (it propagates only if no intervening middleware copies the request); failing both, the label is "unmatched". The status defaults to 200 when the handler wrote a body without an explicit WriteHeader, matching RequestLogger's convention.
func Recoverer ¶
func Recoverer(logger *slog.Logger) Middleware
Recoverer converts a panic in a downstream handler into a 500 response and a logged error (with stack trace and request id) instead of crashing the server or leaking the panic value to the client.
func RequestLogger ¶
func RequestLogger(logger *slog.Logger) Middleware
RequestLogger logs one structured line per request (method, path, status, bytes, duration, request id) using the provided logger. It wraps the ResponseWriter to capture the status and size.
func SecurityHeaders ¶
func SecurityHeaders(hsts string) Middleware
SecurityHeaders sets baseline security response headers on every response, and HSTS only over an effective-HTTPS request. The headers are applied before the handler runs so they survive on every status code.
hsts is the precomputed Strict-Transport-Security value (e.g. "max-age=15552000; includeSubDomains") or "" when HSTS is disabled; the header is emitted only when hsts is non-empty AND the effective scheme is https (IsHTTPS, set by ForwardedHeaders), so it is never sent over the plain-HTTP internal hop behind a TLS-terminating proxy.
func Timeout ¶
func Timeout(d time.Duration) Middleware
Timeout bounds each request's context to d. Handlers observe cancellation via the request context. A context deadline (rather than http.TimeoutHandler) is used so streaming responses (HTMX SSE, hijacking) keep working.