livery_ratelimit (livery v0.2.0)
View SourcePer-key rate-limiting / throttling middleware (token bucket).
Each client (by default identified by its Authorization bearer token) gets
a token bucket: Capacity tokens that refill at RefillPerSec. A request
consumes one token; when the bucket is empty the request is shed with
429 Too Many Requests. A request with no key passes through unlimited.
Build the stack entry with limiter/2,3 (which allocates an isolated
keyspace):
Stack = [
{livery_ratelimit, livery_ratelimit:limiter(100, 10)} %% burst 100, 10/s
]."N requests per minute" maps to limiter(N, N/60). Per-key state lives in
the supervised livery_ratelimit_store ETS table; the raw key is never
stored (it is SHA-256 hashed). Responses carry
RateLimit-Limit/-Remaining/-Reset (and Retry-After on a 429)
unless headers => false.
Security: the default key is the bearer token, which the client controls.
That gives per-credential quotas, NOT protection against an unauthenticated
flood: a client rotating tokens gets a fresh bucket each time. Client IP is
not yet surfaced by the wire libs, so for flood protection key on a trusted
identity via a custom key fun (an authenticated user id, or a forwarded-IP
header you trust because you terminate behind a known proxy). The store caps
its key count (see livery_ratelimit_store) so distinct-key floods bound
memory regardless.
Summary
Types
-type key_fun() :: fun((livery_req:req()) -> binary() | undefined).
Functions
-spec call(livery_req:req(), livery_middleware:next(), state()) -> livery_resp:resp().
Throttle the request by its key, or pass through when keyless.
-spec limiter(non_neg_integer(), number()) -> state().
Build a limiter: Capacity burst tokens refilling at RefillPerSec.
-spec limiter(non_neg_integer(), number(), map()) -> state().
limiter/2 with options: name, key (a req -> binary | undefined
fun, default the bearer token), status (default 429), body, and
headers (default true).