PhoenixKitSync.RateLimit (PhoenixKitSync v0.3.0)

Copy Markdown View Source

The per-minute request limit of a connection, on both sides of it.

Sender. check/1 counts one request against the connection's rate_limit_requests_per_minute and refuses the request over it with the seconds until the minute frees. PhoenixKitSync.Connections.authorize_request/3 calls it, and nothing else does: an entry point is limited because it is authorized, not because it remembered to count.

The counter is core's Hammer backend (PhoenixKit.Users.RateLimiter.Backend, started by core's supervisor): a fixed window on the wall clock, held in ETS per node. A sender that runs on several nodes allows the limit on each of them, and a restart empties it. A limit of 0 (or nil) is no limit.

Receiver. A sender over its limit answers 429 with Retry-After (seconds), or "error_code": "rate_limited" with "retry_after" on a socket. next_wait/2 turns what the sender asked for into the wait this site is willing to make: the value is the peer's, so it is clamped to max_wait/0, and the waits of one request together stay within wait_budget/0. Both are seconds and come from app env:

config :phoenix_kit_sync,
  rate_limit_max_wait_seconds: 60,
  rate_limit_wait_budget_seconds: 300

Summary

Functions

Counts one request under connection.

Counts one request in key's current window of window_ms, allowing limit of them. check/1 with the window as an argument.

The longest single wait, in seconds (rate_limit_max_wait_seconds, default 60).

The seconds to wait before asking a rate-limited sender again.

The most one request waits in total, in seconds (rate_limit_wait_budget_seconds, default 300).

Types

refusal()

@type refusal() :: {:error, {:rate_limited, pos_integer()}}

Functions

check(connection)

@spec check(PhoenixKitSync.Connection.t()) :: :ok | refusal()

Counts one request under connection.

Returns :ok, or {:error, {:rate_limited, seconds}} for a request past the connection's limit in the current minute, with the seconds until that minute ends.

hit(key, limit, window_ms \\ 60000)

@spec hit(String.t(), pos_integer(), pos_integer()) :: :ok | refusal()

Counts one request in key's current window of window_ms, allowing limit of them. check/1 with the window as an argument.

max_wait()

@spec max_wait() :: pos_integer()

The longest single wait, in seconds (rate_limit_max_wait_seconds, default 60).

next_wait(retry_after, waited \\ 0)

@spec next_wait(term(), non_neg_integer()) :: {:wait, pos_integer()} | :give_up

The seconds to wait before asking a rate-limited sender again.

retry_after is what the sender sent (a header value, a JSON number, or nothing) and waited the seconds this request has waited already. Returns {:wait, seconds} with the value clamped between 1 and max_wait/0, or :give_up when that wait would pass wait_budget/0.

A value that is not a whole number of seconds (missing, text, an HTTP date) reads as 5 seconds.

wait_budget()

@spec wait_budget() :: pos_integer()

The most one request waits in total, in seconds (rate_limit_wait_budget_seconds, default 300).