Documentation
¶
Overview ¶
Package middleware ships zip's canonical generic middleware stack. Use these via app.Use(middleware.Recover(), middleware.RequestID(), ...).
Every middleware here is a zip.Handler (NOT a raw fiber.Handler) so the user-facing handler signature stays uniform.
Auth-specific middleware (JWT validation, identity-header stripping) lives in github.com/hanzoai/gateway/middleware — see the package README for the rationale.
Index ¶
- func CORS(cfg CORSConfig) zip.Handler
- func Logger(base luxlog.Logger) zip.Handler
- func MaxBody(n int) zip.Handler
- func ProductionHeaders(cfg ProductionHeadersConfig) zip.Handler
- func RateLimit(cfg RateLimitConfig) zip.Handler
- func Recover() zip.Handler
- func RequestID() zip.Handler
- func Telemetry(sink O11ySink) zip.Handler
- func Timeout(d time.Duration) zip.Handler
- type Breaker
- type BreakerConfig
- type BreakerState
- type CORSConfig
- type O11ySink
- type ProductionHeadersConfig
- type RateLimitConfig
- type Stats
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Logger ¶
Logger logs each request with method, path, status, duration. Adds request_id / org / user to the request-scoped logger via SetLog.
func ProductionHeaders ¶ added in v1.7.3
func ProductionHeaders(cfg ProductionHeadersConfig) zip.Handler
ProductionHeaders stamps the production response-header posture on every response. Register it in the app.Use chain (mounted at "/") so it runs before the business handlers and its headers ride the response out on the success, error, AND 404 paths alike.
It emits three orthogonal signals plus a security floor:
- Server white-label brand by Host (never the framework name)
- X-Api-Version the API contract/build version (brand-neutral key)
- X-Content-Type-Options nosniff (always)
- Strict-Transport-Security when HSTS is set
X-Request-Id is owned by RequestID() — register it in the same chain; the two are orthogonal. ProductionHeaders never emits X-Powered-By or any framework/version string.
The per-response Server set overrides the engine's default Server name on the wire (fasthttp routes Set("Server", …) through SetServer), so a lux request is never served "hanzo" and no response leaks "fasthttp"/"zip".
func RateLimit ¶
func RateLimit(cfg RateLimitConfig) zip.Handler
RateLimit returns a per-org (or per-IP fallback) token-bucket limiter. Buckets are kept in-memory; suitable for single-pod deployments. Multi-pod deployments should use a distributed limiter at the gateway.
func Recover ¶
Recover catches handler panics and turns them into a 500 JSON response. Always include this first in the chain.
func RequestID ¶
RequestID injects an X-Request-Id header (incoming if present; else 16-byte hex). Available via c.RequestID().
Types ¶
type Breaker ¶
type Breaker struct {
// contains filtered or unexported fields
}
Breaker is one circuit-breaker instance. Safe for concurrent use.
Breaker is intentionally per-process: it is a memoized recent-failure view, not a coordinated cross-replica policy. N replicas of the same service running their own breaker is the correct shape — each replica sheds load it cannot serve. There is no shared state, no leader, no coordination overhead.
func NewBreaker ¶
func NewBreaker(cfg BreakerConfig) *Breaker
NewBreaker constructs a Breaker with defaults applied.
func (*Breaker) Allow ¶
Allow reports whether the breaker permits a new request right now. Callers that need to wrap arbitrary code (not a zip handler) can use this directly and then call Report(err, status) when the work completes.
In half-open state, Allow returns true for exactly one in-flight request; subsequent callers see false until the in-flight one reports.
func (*Breaker) Middleware ¶
Breaker returns the zip middleware form of b. The middleware:
- Calls b.Allow() before delegating to the next handler.
- If Allow returns false, short-circuits with 503 and a brief {"error":"upstream unavailable"} body. NO retry. NO queue. Fail fast — let the client back off.
- Otherwise runs c.Continue() and reports the outcome.
The breaker is single-purpose: it sheds load when the protected resource is in trouble. Combine with other middleware (Logger, Recover, Timeout) the usual way.
func (*Breaker) Report ¶
Report records the outcome of a request that previously Allow()ed. err is the handler error; status is the final response status. The breaker uses cfg.FailureClassifier to decide whether it was a failure.
func (*Breaker) State ¶
func (b *Breaker) State() BreakerState
State returns the breaker's current state. Cheap; lock-free.
type BreakerConfig ¶
type BreakerConfig struct {
// FailureThreshold is the number of consecutive failures (or the
// failure count within the rolling window) that trips the breaker.
// Default: 5.
FailureThreshold int
// SuccessThreshold is the number of consecutive successes in
// half-open that closes the breaker. Default: 1 (the first
// half-open success closes immediately).
SuccessThreshold int
// OpenWindow is how long the breaker stays open before transitioning
// to half-open. Default: 5s.
OpenWindow time.Duration
// FailureClassifier classifies an outcome as a failure. By default a
// non-nil handler error OR a 5xx response body is a failure.
FailureClassifier func(err error, status int) bool
// Now is the clock. Inject for tests. Default: time.Now.
Now func() time.Time
// OnStateChange, if set, is called every time the breaker transitions.
// Useful for metrics. Called from the breaker's critical section, so
// the callback MUST NOT block.
OnStateChange func(prev, next BreakerState)
}
BreakerConfig configures a single circuit breaker. The breaker is a composable middleware: failures are anything that returns a non-nil error OR writes a 5xx status. The "target" abstraction is the caller's — wrap one breaker per upstream where you want isolation.
type BreakerState ¶
type BreakerState int32
BreakerState is the externally observable state of a circuit breaker.
const ( // BreakerClosed allows traffic. Failures are counted toward the trip // threshold. BreakerClosed BreakerState = iota // BreakerOpen rejects every request with 503 until the open window // elapses, at which point the breaker transitions to half-open. BreakerOpen // BreakerHalfOpen permits one request at a time. A success closes // the breaker; a failure re-opens it. BreakerHalfOpen )
type CORSConfig ¶
type CORSConfig struct {
AllowOrigins []string // "*" or explicit list. Default: ["*"]
AllowMethods []string // Default: GET,POST,PUT,DELETE,PATCH,OPTIONS
AllowHeaders []string // Default: Content-Type,Authorization,X-Request-Id
ExposeHeaders []string
AllowCreds bool
MaxAge int // seconds
}
CORSConfig configures the CORS middleware.
type O11ySink ¶
type O11ySink interface {
// Record reports a single request to the o11y backend.
Record(method, path string, status int, dur time.Duration, attrs map[string]string)
}
O11ySink is the minimum interface zip's telemetry middleware consumes. Real implementations live in hanzoai/insights / o11y SDKs. nil sink = no-op middleware.
type ProductionHeadersConfig ¶ added in v1.7.3
type ProductionHeadersConfig struct {
// Brand resolves a request Host to the white-label brand id emitted in the
// Server response header ("hanzo", "lux", "zoo", ...). It is injected by the
// service so this framework stays brand-agnostic: the brand registry has ONE
// home in the service (e.g. cloud.BrandForHostOK). Return "" when the Host
// matches no known brand — Neutral is emitted then. When Brand itself is nil,
// every response uses Neutral.
Brand func(host string) string
// Neutral is the Server value used when Brand is nil or returns "". It MUST
// be a brand id or a neutral platform token — NEVER a framework name
// (fasthttp / fiber / zip), which would leak the stack. Defaults to "api".
Neutral string
// Version is emitted as the brand-neutral X-Api-Version header (the API
// contract / build version, for support correlation). Empty omits the header.
Version string
// HSTS enables Strict-Transport-Security (max-age 63072000; includeSubDomains).
HSTS bool
}
ProductionHeadersConfig configures the production response-header posture — the Stripe/Cloudflare/GitHub-grade signals plus a security floor, stamped on every response (success, error, and 404) by ProductionHeaders.
type RateLimitConfig ¶
type RateLimitConfig struct {
// Limit is the number of requests permitted per Window.
Limit int
// Window is the time window (e.g. 1*time.Minute).
Window time.Duration
// KeyFn extracts the bucket key from the request. Default: c.Org()
// if present, else c.Fiber().IP().
KeyFn func(c *zip.Ctx) string
}
RateLimitConfig configures the per-key token bucket.