NAME
Punk::RateLimit - rate limiting and IP blocking over Hyperman's shared arena
SYNOPSIS
use Punk;
# a loose limit on everything, keyed by client IP
rate_limit limit => 300, window => 60;
# a tighter one on the API, keyed by an API-key header
rate_limit for => '/api', by => 'header:X-Api-Key',
limit => 60, window => 60, tag => 'api';
# or a custom identity
rate_limit by => sub { my ($c) = @_; $c->session->{user} }, limit => 20;
# block an abuser from a handler; the edge drops it next time
post '/login' => sub {
my ($c) = @_;
if (too_many_failures($c)) { $c->block_ip(undef, 3600); }
...
};
DESCRIPTION
rate_limit installs a before_dispatch that answers 429 Too Many Requests (with Retry-After and the X-RateLimit-* headers) when a caller exceeds the limit for the rule. The counters live in Hyperman's shared arena, mapped before its workers fork, so a limit is exact across the whole pool rather than per worker. It is by the client IP by default; by => 'header:NAME' keys on a request header, and by => sub { ... } on whatever the coderef returns for a context. for scopes a rule to a path prefix, tag names its counter namespace. Declare it more than once for layered limits.
Blocking is separate and cheaper: $c->block_ip($ip, $ttl) adds an IP to the same arena's denylist, and Hyperman drops it at accept - before a byte is read - on its next connection. $c->unblock_ip($ip) lifts it. Both default $ip to the current request's REMOTE_ADDR. $c->rate_hit($key, $limit, $window) is the raw counter check, returning ($ok, $remaining, $reset).
Everything fails open: with no Hyperman >= ABI v3 under the application the limiter allows every request and blocking is a no-op, so it is never the reason a good request is refused.