middleware

package
v0.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 17, 2026 License: Apache-2.0 Imports: 10 Imported by: 1

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
  • Guard: Composable middleware chain

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

Constants

This section is empty.

Variables

This section is empty.

Functions

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

func ClientIP(r *http.Request) string

ClientIP extracts the real client IP using the default extractor. Configure trusted proxies via SetTrustedProxies.

func Recovery

func Recovery(next http.Handler) http.Handler

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 RequestLogger

func RequestLogger(skipPaths ...string) func(http.Handler) http.Handler

RequestLogger logs HTTP requests with method, path, status, duration, and client IP. Skips logging for specified paths (e.g. /health 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 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))

func (*Guard) Use

func (g *Guard) Use(mw ...func(http.Handler) http.Handler)

Use adds middleware to the guard chain. Nil middleware are silently skipped.

func (*Guard) Wrap

func (g *Guard) Wrap(h http.Handler) http.Handler

Wrap applies all configured middleware to a handler. On a nil receiver, returns the handler unchanged.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL