ratelimit

package
v0.14.7 Latest Latest
Warning

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

Go to latest
Published: Apr 26, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package ratelimit provides the rate-limit primitives nexus uses to throttle endpoints: a Limit shape, a Store interface (pluggable to memory, Redis, or any backend), and a token-bucket MemoryStore.

Integration lives in the nexus package (AsQuery/AsMutation accept a RateLimit option; an auto-attached middleware consults the store per request). Dashboard reads + writes through the store so operators can tune limits live without redeploying.

Index

Constants

View Source
const GlobalKey = "_global"

GlobalKey is the well-known key under which an app-wide rate limit is stored. Every request consults this bucket in addition to its per-op key; exhausting either denies the call. Kept as a constant so the dashboard can special-case the row ("Global" heading).

Variables

This section is empty.

Functions

func ClientIPFromCtx

func ClientIPFromCtx(ctx context.Context) string

ClientIPFromCtx returns the caller's IP a transport put in ctx, or empty when absent.

func GinMiddleware

func GinMiddleware(store Store, key string, scopeFn func(*gin.Context) string) gin.HandlerFunc

GinMiddleware returns a gin.HandlerFunc that enforces the named bucket against store. Use it for global (key=GlobalKey) or per-route limits. Denial aborts with 429 + Retry-After header + JSON error body — same shape a client library would expect regardless of transport.

scopeFn returns the per-request bucket scope (IP for PerIP limits, "" otherwise). Defaults to c.ClientIP when nil — good for most apps.

func NewMiddleware

func NewMiddleware(store Store, key string, limit Limit) middleware.Middleware

NewMiddleware returns a transport-agnostic middleware bundle that enforces rate limits against store under key. The same bundle can be attached to any transport via nexus.Use: gin-based enforcement for REST + WS upgrades, graph.FieldMiddleware for GraphQL resolvers.

The declared Limit is registered with the store at middleware-create time so the dashboard can show it as a baseline; operators can tune the effective limit live via the Rate limits tab.

rl := ratelimit.NewMiddleware(
    store,
    "adverts.createAdvert",
    ratelimit.Limit{RPM: 30, Burst: 5},
)
fx.Provide(
    nexus.AsMutation(NewCreateAdvert, nexus.Use(rl)),
    nexus.AsRest("POST", "/quick", NewQuick, nexus.Use(rl)),
)

func WithClientIP

func WithClientIP(ctx context.Context, ip string) context.Context

WithClientIP returns ctx carrying ip. Transports (gin REST handler, gql adapter, WS upgrade) call this so middleware that scopes buckets per IP can find it.

Types

type Limit

type Limit struct {
	RPM   int  `json:"rpm"`
	Burst int  `json:"burst,omitempty"`
	PerIP bool `json:"perIP,omitempty"`
}

Limit describes a per-endpoint rate ceiling. RPM is the steady-state request-per-minute target; Burst is the short-term capacity (defaults to max(RPM/6, 1) when zero — a 10-second burst window). PerIP, when true, scopes the bucket to the caller's IP address so a single tenant can't starve the endpoint for others.

func (Limit) EffectiveBurst

func (l Limit) EffectiveBurst() int

EffectiveBurst returns Burst if set, else a sensible default derived from RPM. Keeps callers from sprinkling max-logic everywhere.

func (Limit) Zero

func (l Limit) Zero() bool

Zero reports whether the limit is effectively disabled (RPM <= 0). Used by the middleware wrapper to short-circuit when no limit applies.

type MemoryStore

type MemoryStore struct {
	// contains filtered or unexported fields
}

MemoryStore is the default Store. Token-bucket per (key, scope): the bucket refills at Limit.RPM/60 tokens per second and holds at most Limit.EffectiveBurst() tokens. scope="" when PerIP is false.

func NewMemoryStore

func NewMemoryStore() *MemoryStore

NewMemoryStore returns an in-process token-bucket Store. Safe for single-instance deployments and tests; replace with a Redis-backed store when you need counters to survive restarts or shard across replicas.

func (*MemoryStore) Allow

func (s *MemoryStore) Allow(_ context.Context, key, scope string) (bool, time.Duration)

func (*MemoryStore) Configure

func (s *MemoryStore) Configure(_ context.Context, key string, limit Limit) (Record, error)

func (*MemoryStore) Declare

func (s *MemoryStore) Declare(key string, limit Limit)

func (*MemoryStore) Reset

func (s *MemoryStore) Reset(_ context.Context, key string) error

func (*MemoryStore) Snapshot

func (s *MemoryStore) Snapshot(_ context.Context) []Record

type Record

type Record struct {
	Key       string `json:"key"`
	Declared  Limit  `json:"declared"`
	Effective Limit  `json:"effective"`
	// Overridden is true when Effective differs from Declared — the
	// operator tuned the limit live. Dashboard surfaces this as a badge
	// so someone reading the config doesn't mistake UI state for source
	// of truth.
	Overridden bool `json:"overridden,omitempty"`
}

Record bundles a store entry for Snapshot listings — the declared baseline (from code) plus the effective limit (possibly overridden via the dashboard) for a single endpoint key.

type Store

type Store interface {
	// Declare registers the compile-time declared limit for key. Called
	// at boot by the nexus auto-mount; overridden limits persist across
	// re-declarations so boot doesn't wipe operator tuning.
	Declare(key string, limit Limit)

	// Configure replaces the effective limit for key — the operator
	// override path. Returns the record it produced so the caller
	// (dashboard HTTP) can echo it back to the browser.
	Configure(ctx context.Context, key string, limit Limit) (Record, error)

	// Reset drops any operator override for key, reverting to the
	// declared baseline. No-op when nothing was overridden.
	Reset(ctx context.Context, key string) error

	// Allow is the enforcement call. scope is the IP-or-global bucket
	// identifier; the store combines it with key to isolate buckets.
	// Returns (ok, retryAfter). retryAfter is zero when ok=true.
	Allow(ctx context.Context, key, scope string) (bool, time.Duration)

	// Snapshot returns every key's current Record. Dashboard consumes
	// this on GET /__nexus/ratelimits.
	Snapshot(ctx context.Context) []Record
}

Store is the backend contract. Memory + Redis variants implement it. All methods are safe for concurrent use.

Jump to

Keyboard shortcuts

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