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
- func ClientIPFromCtx(ctx context.Context) string
- func GinMiddleware(store Store, key string, scopeFn func(*gin.Context) string) gin.HandlerFunc
- func NewMiddleware(store Store, key string, limit Limit) middleware.Middleware
- func WithClientIP(ctx context.Context, ip string) context.Context
- type Limit
- type MemoryStore
- func (s *MemoryStore) Allow(_ context.Context, key, scope string) (bool, time.Duration)
- func (s *MemoryStore) Configure(_ context.Context, key string, limit Limit) (Record, error)
- func (s *MemoryStore) Declare(key string, limit Limit)
- func (s *MemoryStore) Reset(_ context.Context, key string) error
- func (s *MemoryStore) Snapshot(_ context.Context) []Record
- type Record
- type Store
Constants ¶
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 ¶
ClientIPFromCtx returns the caller's IP a transport put in ctx, or empty when absent.
func GinMiddleware ¶
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)),
)
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 ¶
EffectiveBurst returns Burst if set, else a sensible default derived from RPM. Keeps callers from sprinkling max-logic everywhere.
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) Declare ¶
func (s *MemoryStore) Declare(key string, limit Limit)
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.