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 ¶
- Variables
- 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 CSPDirective
- type ContentSecurityPolicy
- 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 SecurityHeadersWithPolicy(hsts string, csp ContentSecurityPolicy) Middleware
- func Timeout(d time.Duration) Middleware
Constants ¶
This section is empty.
Variables ¶
var FrameAncestorsSelf = DefaultCSP()
FrameAncestorsSelf is a package-level value of DefaultCSP for callers that want to reference or extend the default. SecurityHeaders and httpserver.New do not read it, so mutating its slices cannot weaken their framing policy.
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 CSPDirective ¶ added in v0.6.0
CSPDirective is one Content-Security-Policy directive: a name such as "script-src" and the source expressions that follow it, such as "'self'".
func (CSPDirective) String ¶ added in v0.6.0
func (d CSPDirective) String() string
String renders the directive as its name followed by its space-separated values; a directive without values renders as the bare name.
type ContentSecurityPolicy ¶ added in v0.6.0
type ContentSecurityPolicy []CSPDirective
ContentSecurityPolicy is an ordered list of Content-Security-Policy directives. Order is the caller's and is preserved when rendering, so the emitted header is deterministic and a test can assert it byte-for-byte. A policy is a compile-time constant of the consuming application; it carries no per-request state such as a nonce.
func DefaultCSP ¶ added in v0.6.0
func DefaultCSP() ContentSecurityPolicy
DefaultCSP returns a fresh copy of the historical default policy: it only restricts framing to the page's own origin and leaves every fetch directive open. SecurityHeaders and httpserver.New use it when no policy is supplied. Each call allocates new slices, so mutating one result never changes the framing policy another caller receives.
func (ContentSecurityPolicy) String ¶ added in v0.6.0
func (p ContentSecurityPolicy) String() string
String renders the policy as a Content-Security-Policy header value: directives in slice order, joined by "; ", each as its name followed by its space-separated values. A nil or empty policy renders "".
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. It emits the historical DefaultCSP policy; use SecurityHeadersWithPolicy to supply a stricter Content-Security-Policy.
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 SecurityHeadersWithPolicy ¶ added in v0.6.0
func SecurityHeadersWithPolicy(hsts string, csp ContentSecurityPolicy) Middleware
SecurityHeadersWithPolicy is SecurityHeaders with a caller-supplied Content-Security-Policy. The policy is rendered once, when the middleware is built, and emitted verbatim on every response; a nil or empty csp renders "" and the Content-Security-Policy header is then omitted entirely. The other headers and the HSTS gating are identical to SecurityHeaders.
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.