Documentation
¶
Overview ¶
Package middleware provides reusable HTTP middleware for production hardening.
Components include:
- ClientIPExtractor: Extracts real client IPs behind trusted reverse proxies
- RateLimiter: Global and per-key rate limiting with configurable key functions
- ConnLimiter: Concurrent connection limiting for WebSocket endpoints
- OriginChecker: Origin allowlist for WebSocket upgrade requests
- CORS: Origin-aware CORS header middleware
- Recovery: Panic recovery with structured logging
- RequestLogger: Structured HTTP request logging (includes request ID when available)
- RequestID: Request ID generation/propagation via X-Request-Id header
- BodyLimiter: Request body size limiting via http.MaxBytesReader
- HealthCheck: Health/readiness endpoint handler for load balancers and k8s probes
- Guard: Composable middleware chain
Helpers:
- ApplyDefaults: Sets sensible timeout defaults on http.Server
All components are nil-safe: a nil component acts as a no-op passthrough. No application-specific imports — designed for embedding in any Go HTTP server.
Index ¶
- func ApplyDefaults(srv *http.Server)
- func CORS(checker *OriginChecker) func(http.Handler) http.Handler
- func ClientIP(r *http.Request) string
- func ContextWithRequestID(ctx context.Context, id string) context.Context
- func Recovery(next http.Handler) http.Handler
- func RequestIDFromContext(ctx context.Context) string
- func RequestLogger(skipPaths ...string) func(http.Handler) http.Handler
- func SetTrustedProxies(cidrs []string)
- type BodyLimiter
- type ClientIPExtractor
- type ConnLimiter
- type Guard
- type HealthCheck
- type HealthOption
- type OriginChecker
- type RateLimitConfig
- type RateLimiter
- type RequestID
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ApplyDefaults ¶ added in v0.0.7
ApplyDefaults sets sensible timeout defaults on an http.Server, only overwriting fields that are zero-valued.
Defaults:
- ReadTimeout: 10s
- WriteTimeout: 30s
- IdleTimeout: 120s
- ReadHeaderTimeout: 5s
For SSE or long-lived streaming endpoints, callers should either set WriteTimeout to a non-zero value before calling ApplyDefaults (it won't overwrite), or set WriteTimeout = 0 after calling this function to disable the write deadline for streaming responses.
func CORS ¶
func CORS(checker *OriginChecker) func(http.Handler) http.Handler
CORS returns middleware that sets CORS headers based on an OriginChecker.
Behavior:
- If checker is nil (no allowlist): Access-Control-Allow-Origin: *
- If checker is set and the request Origin matches: reflect the origin back
- If checker is set and the request Origin doesn't match: no CORS headers (browser will block the response)
- OPTIONS preflight requests are handled and return 204
This replaces the naive "Access-Control-Allow-Origin: *" pattern with origin validation. The same OriginChecker used for WebSocket origin checks is reused here for consistency.
func ClientIP ¶
ClientIP extracts the real client IP using the default extractor. Configure trusted proxies via SetTrustedProxies.
func ContextWithRequestID ¶ added in v0.0.7
ContextWithRequestID returns a new context with the given request ID.
func Recovery ¶
Recovery returns middleware that recovers from panics in downstream handlers. On panic, it logs the error and stack trace, then returns 500 to the client. Without this, a single bad request can crash the entire server.
func RequestIDFromContext ¶ added in v0.0.7
RequestIDFromContext extracts the request ID from context, or "" if absent.
func RequestLogger ¶
RequestLogger logs HTTP requests with method, path, status, duration, and client IP. When a request ID is present in the context (set by the RequestID middleware), it is automatically included in the log output as "request_id". Skips logging for specified paths (e.g. /healthz for noisy liveness probes).
func SetTrustedProxies ¶
func SetTrustedProxies(cidrs []string)
SetTrustedProxies configures the default (package-level) extractor. This is a convenience for simple use cases; prefer NewClientIPExtractor for instance isolation.
Types ¶
type BodyLimiter ¶ added in v0.0.7
type BodyLimiter struct {
// contains filtered or unexported fields
}
BodyLimiter limits the size of incoming request bodies using http.MaxBytesReader. When the handler reads past the limit, MaxBytesReader returns an error and the request fails.
Nil-safe: a nil BodyLimiter passes requests through unchanged.
func NewBodyLimiter ¶ added in v0.0.7
func NewBodyLimiter(maxBytes int64) *BodyLimiter
NewBodyLimiter creates a limiter with the given max body size in bytes. Returns nil if maxBytes <= 0 (caller should skip the middleware).
func (*BodyLimiter) Middleware ¶ added in v0.0.7
func (b *BodyLimiter) Middleware(next http.Handler) http.Handler
Middleware returns an HTTP middleware that limits request body size. On a nil receiver, returns the handler unchanged.
type ClientIPExtractor ¶
type ClientIPExtractor struct {
// contains filtered or unexported fields
}
ClientIPExtractor extracts the real client IP from requests, honoring X-Forwarded-For and X-Real-IP headers only when the direct connection comes from a trusted proxy CIDR.
Usage:
// Trust Caddy on localhost and Docker bridge network
extractor := NewClientIPExtractor([]string{"127.0.0.1/32", "172.17.0.0/16", "::1/128"})
ip := extractor.ClientIP(r)
// Trust all proxies (suitable for single-proxy deployments)
extractor := NewClientIPExtractor(nil)
func NewClientIPExtractor ¶
func NewClientIPExtractor(cidrs []string) *ClientIPExtractor
NewClientIPExtractor creates an extractor with the given trusted proxy CIDRs. When cidrs is nil/empty, all proxies are trusted (backwards-compatible default).
func (*ClientIPExtractor) ExtractClientIP ¶
func (e *ClientIPExtractor) ExtractClientIP(r *http.Request) string
ExtractClientIP extracts the real client IP from the request.
If trusted proxies are configured, X-Forwarded-For is only honored when the direct connection (RemoteAddr) comes from a trusted proxy CIDR. Otherwise, the direct RemoteAddr is used.
If no trusted proxies are configured (default), X-Forwarded-For is always trusted (backwards-compatible for deployments behind a proxy).
type ConnLimiter ¶
type ConnLimiter struct {
// contains filtered or unexported fields
}
ConnLimiter limits the number of concurrent active requests to a handler. Designed for WebSocket endpoints where each connection holds resources for the lifetime of the session.
When the limit is reached, new requests receive 503 Service Unavailable. The counter decrements when the request handler returns (connection closes).
func NewConnLimiter ¶
func NewConnLimiter(max int64) *ConnLimiter
NewConnLimiter creates a limiter with the given max concurrent connections. Pass 0 for unlimited (returns nil — caller should skip the middleware).
func (*ConnLimiter) Active ¶
func (c *ConnLimiter) Active() int64
Active returns the current number of active connections.
func (*ConnLimiter) Middleware ¶
func (c *ConnLimiter) Middleware(next http.Handler) http.Handler
Middleware returns an HTTP middleware that enforces the connection limit.
type Guard ¶
type Guard struct {
// contains filtered or unexported fields
}
Guard composes multiple middleware into a single wrapper. Middleware are applied in the order they are added via Use — the first middleware added is the outermost (runs first on request, last on response).
Usage:
g := &Guard{}
g.Use(originChecker.Middleware, rateLimiter.Middleware(nil), auth.Middleware, connLimiter.Middleware)
http.Handle("/ws", g.Wrap(wsHandler))
type HealthCheck ¶ added in v0.0.7
type HealthCheck struct {
// contains filtered or unexported fields
}
HealthCheck is an HTTP handler that serves health/readiness endpoints for load balancers and Kubernetes probes. It implements http.Handler and should be mounted directly on a mux, not used as middleware.
Nil-safe: a nil *HealthCheck returns 404.
func NewHealthCheck ¶ added in v0.0.7
func NewHealthCheck(opts ...HealthOption) *HealthCheck
NewHealthCheck creates a health check handler with the given options. Default path is "/healthz".
func (*HealthCheck) Path ¶ added in v0.0.7
func (h *HealthCheck) Path() string
Path returns the configured endpoint path for mux registration.
func (*HealthCheck) ServeHTTP ¶ added in v0.0.7
func (h *HealthCheck) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler. On a nil receiver, returns 404.
type HealthOption ¶ added in v0.0.7
type HealthOption func(*HealthCheck)
HealthOption configures the health check handler.
func WithPath ¶ added in v0.0.7
func WithPath(path string) HealthOption
WithPath sets the health endpoint path (default "/healthz").
func WithReadyFunc ¶ added in v0.0.7
func WithReadyFunc(fn func() bool) HealthOption
WithReadyFunc sets an optional readiness callback. When set and returning false, the endpoint returns 503 with {"status":"not ready"}.
type OriginChecker ¶
type OriginChecker struct {
// contains filtered or unexported fields
}
OriginChecker rejects requests whose Origin header doesn't match an allowlist. Designed for WebSocket endpoints where CORS headers alone don't prevent cross-origin connections.
Matching rules:
- Exact match on scheme+host (port-insensitive for 80/443)
- Wildcard subdomain: "*.example.com" matches "foo.example.com"
- "localhost" matches any localhost origin regardless of port
- Empty allowlist = allow all (no-op)
- Missing Origin header = blocked (unless allowlist is empty)
func NewOriginChecker ¶
func NewOriginChecker(origins []string) *OriginChecker
NewOriginChecker creates a checker from a list of allowed origins. Accepts formats: "https://example.com", "*.example.com", "localhost". Returns nil if the list is empty (caller should skip the check).
func (*OriginChecker) Check ¶
func (c *OriginChecker) Check(origin string) bool
Check returns true if the origin is allowed.
func (*OriginChecker) Middleware ¶
func (c *OriginChecker) Middleware(next http.Handler) http.Handler
Middleware returns an HTTP middleware that rejects disallowed origins. Only applies to WebSocket upgrade requests (Connection: Upgrade). Non-upgrade requests pass through unchanged.
type RateLimitConfig ¶
type RateLimitConfig struct {
GlobalPerSec float64 // max requests/sec globally (0 = unlimited)
PerKeyPerSec float64 // max requests/sec per key (0 = unlimited)
PerKeyBurst int // burst allowance per key
KeyLimiterTTL time.Duration // cleanup interval for stale per-key limiters
}
RateLimitConfig controls rate limiting.
func DefaultRateLimitConfig ¶
func DefaultRateLimitConfig() RateLimitConfig
DefaultRateLimitConfig returns sensible defaults.
type RateLimiter ¶
type RateLimiter struct {
Config RateLimitConfig
// OnRejected is called when a request is rate-limited.
// The key argument is the rate limit key (e.g., IP address, subject ID).
OnRejected func(key string)
// contains filtered or unexported fields
}
RateLimiter enforces global and per-key rate limits.
func NewRateLimiter ¶
func NewRateLimiter(cfg RateLimitConfig) *RateLimiter
NewRateLimiter creates a rate limiter. Returns nil if both limits are 0.
func (*RateLimiter) Allow ¶
func (rl *RateLimiter) Allow(key string) bool
Allow checks both global and per-key rate limits. Returns false if rejected.
func (*RateLimiter) Middleware ¶
func (rl *RateLimiter) Middleware(keyFunc func(*http.Request) string) func(http.Handler) http.Handler
Middleware returns an HTTP middleware that enforces rate limits. keyFunc extracts the rate limit key from the request. If nil, defaults to ClientIP.
type RequestID ¶ added in v0.0.7
type RequestID struct{}
RequestID generates or propagates request IDs via the X-Request-Id header. If the incoming request has an X-Request-Id header, it is preserved. Otherwise, a new 32-character hex string (16 random bytes) is generated. The ID is stored in the request context and set on the response header.
func NewRequestID ¶ added in v0.0.7
func NewRequestID() *RequestID
NewRequestID creates a RequestID middleware instance.