middleware

package
v1.2.1 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2026 License: MIT Imports: 9 Imported by: 0

README

zip middleware

Generic middleware for the zip framework. Auth-specific middleware (JWT validation, identity-header stripping) lives in github.com/hanzoai/gateway/middleware — see "Why auth lives in gateway" below.

Available middleware

Name Purpose
Recover() panic → JSON 500
Logger(luxlog.Logger) request log via luxfi/log
RequestID() X-Request-Id propagation
Timeout(d) per-request ctx deadline
MaxBody(n) request size limit
CORS(opts) standard CORS
RateLimit(opts) per-key token bucket
Telemetry(o11yClient) OTel span + request metrics

Why auth lives in gateway

JWT validation + identity-header minting are the responsibility of the gateway subsystem (per HIP-0106). Other subsystems mounted inside the unified cloud binary trust the gateway-minted X-Org-Id header and do NOT re-validate JWTs themselves — re-running JWT validation per-subsystem is wasteful and risks divergent validation rules.

Subsystems that need to ASSERT the request was gateway-minted import github.com/hanzoai/gateway and call gateway.AssertGatewayMinted(c).

Pipeline order

Recommended order for a service that mounts zip behind hanzoai/gateway:

app.Use(
    middleware.Recover(),                // 1. always first
    middleware.RequestID(),              // 2. mint request id
    middleware.Logger(app.Logger()),     // 3. log with request id
    middleware.Telemetry(o11y),          // 4. metrics
    middleware.RateLimit(rlCfg),         // 5. limit
    middleware.CORS(corsCfg),            // 6. last — close to response
)

JWT validation + identity-header stripping/minting is owned by the gateway subsystem; see github.com/hanzoai/gateway/middleware.

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func CORS

func CORS(cfg CORSConfig) zip.Handler

CORS returns the CORS middleware.

func Logger

func Logger(base luxlog.Logger) zip.Handler

Logger logs each request with method, path, status, duration. Adds request_id / org / user to the request-scoped logger via SetLog.

func MaxBody

func MaxBody(n int) zip.Handler

MaxBody refuses requests larger than n bytes with 413.

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

func Recover() zip.Handler

Recover catches handler panics and turns them into a 500 JSON response. Always include this first in the chain.

func RequestID

func RequestID() zip.Handler

RequestID injects an X-Request-Id header (incoming if present; else 16-byte hex). Available via c.RequestID().

func Telemetry

func Telemetry(sink O11ySink) zip.Handler

Telemetry returns middleware that records every request through sink. Use this alongside Logger() (which writes to luxfi/log); Telemetry() is for structured metrics/traces flowing to o11y backends.

func Timeout

func Timeout(d time.Duration) zip.Handler

Timeout sets a per-request deadline via context.WithTimeout. Handlers that respect ctx will be cancelled when it expires.

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

func (b *Breaker) Allow() bool

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

func (b *Breaker) Middleware() zip.Handler

Breaker returns the zip middleware form of b. The middleware:

  1. Calls b.Allow() before delegating to the next handler.
  2. 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.
  3. 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

func (b *Breaker) Report(err error, status int)

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.

func (*Breaker) Stats

func (b *Breaker) Stats() Stats

Stats returns a counter snapshot. Useful for /metrics exposition.

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 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.

type Stats

type Stats struct {
	State          BreakerState
	Requests       uint64
	ShortCircuited uint64
	Failures       uint64
	Successes      uint64
}

Stats is a snapshot of the breaker's counters. Cheap; lock-free.

Jump to

Keyboard shortcuts

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