Skip to main content

rate-limit

rate-limit

Enforces a per-client request rate using the token bucket algorithm. Each client key gets its own in-memory bucket (stored in a concurrent DashMap, created lazily on the client's first request); requests that find the bucket empty are rejected with 429 through the node's limited port. Place it before upstream to shed excess traffic early.

Configuration

All keys are optional; the constructor never fails.

KeyTypeDefaultDescription
requests_per_secondinteger100Sustained refill rate per client key: tokens added per second.
burstinteger= requests_per_secondBucket capacity: maximum requests allowed in a burst.
key_fromstringremote addressUse "header:<name>" to key on a request header instead; any other value keys on the remote address.
type: rate-limit
config:
requests_per_second: 10
burst: 20
key_from: "header:x-api-key"

Behavior

The per-client key is context.request.remote_addr by default, or the first value of the configured header; when the header is absent from a request, the key falls back to the remote address.

Each bucket starts full at burst tokens and is refilled continuously based on elapsed time at requests_per_second, capped at burst. Every request consumes one token:

  • Token available — the request passes through the success port with the Context untouched.
  • Bucket empty — the plugin writes a rejection onto context.response (status 429, JSON body {"error": "rate_limited", "message": "Too many requests"}, content-type: application/json, and a retry-after: 1 header) and exits through the limited port.

Buckets live in process memory: counts are per gateway instance and are lost on restart. The plugin does not write to context.message.

Legacy configs

Older UI builds saved the keys limit, window_s, strategy, and key_by, which the plugin ignores — nodes saved with them run with the defaults above. Re-save the node (the editor now uses the correct keys) or update the YAML to requests_per_second/burst/key_from.

Ports

rate-limit declares three output ports: success, limited (a 429 rejection is prepared), and error (never actually used — the plugin never fails). Like success, limited is a mandatory port: the policy compiler rejects any policy that leaves it unwired. Wire rate-limit.limited straight to client so the prepared 429 reaches the caller instead of continuing into upstream:

edges:
- from: rate-limit.success
to: upstream.in
- from: rate-limit.limited
to: client.in

Errors

This node never fails at execution time: it always returns through success, so its error port is never taken.