NAME
CGI::Application::Plugin::Throttle - Rate-Limiting for CGI::Application
SYNOPSIS
use CGI::Application::Plugin::Throttle;
# Your application
sub setup {
...
# Create a redis handle
my $redis = Redis->new();
# Configure throttling
$self->throttle()->configure(
redis => $redis,
prefix => "REDIS:KEY:PREFIX",
limit => 100,
period => 60,
exceeded => "slow_down_champ"
);
...
}
sub throttle_keys {
my $self = shift;
# do not throttle at all when returning `undef`
return undef if %ENV{DEVELOPMENT};
return (
remote_addr => $ENV{REMOTE_ADDR},
maybe
pwd_recover => $self->_is_password_recovery
);
}
sub throttle_spec {
{ pwd_recover => 1 } =>
{ limit => 5, period => 300, exceeded => 'stay_out' }
{ remote_addr => '127.0.0.1' }
{ limit => 10_000, period => 1, exceeded => 'get_home' }
}
VERSION
This is version '0.7'
DESCRIPTION
This module allows you to enforce a throttle on incoming requests to your application, based upon the remote IP address, or other parameters.
This module stores a count of accesses in a Redis key-store, and once hits exceed the specified threshold the user will be redirected to the run-mode you've specified.
POTENTIAL ISSUES / CONCERNS
Users who share IP addresses, because they are behind a common-gateway for example, will all suffer if the threshold is too low. We attempt to mitigate this by building the key using a combination of the remote IP address, and the remote user-agent.
This module has added great flexibillity to change the parameters being used for generating the redis key. It now also has the posibillity to select different throttle rules specified by filters that need to match the parameters.
METHODS
import
Force the throttle
method into the caller's namespace, and configure the prerun hook which is used by CGI::Application.
new
This method is used internally, and not expected to be invoked externally.
The defaults are setup here, although they can be overridden in the "configure" method.
throttle
Gain access to an instance of this class. This is the method by which you can call methods on this plugin from your CGI::Application derived-class.
count
Returns two values: the number of times the remote client has hit a run mode, along with the maximum allowed visits:
sub your_run_mode
{
my ($self) = (@_);
my( $count, $max ) = $self->throttle()->count();
return( "$count visits seen - maximum is $max." );
}
warning
This method must be called in list context, in scalar context, the result will always be '2'.
configure
This method is what the user will invoke to configure the throttle-limits.
It is expected that within the users CGI::Application "setup" in CGI::Application method there will be code similar to this:
sub setup {
my $self = shift;
my $r = Redis->new();
$self->throttle()->configure( redis => $r,
# .. other options here
)
}
The arguments hash contains the following known keys:
redis
-
A Redis handle object.
limit
-
The maximum number of requests that the remote client may make, in the given period of time.
period
-
The period of time which requests are summed for. The period is specified in seconds and if more than
limit
requests are sent then the client will be redirected. prefix
-
This module uses Redis to store the counts of client requests. Redis is a key-value store, and each key used by this module is given a prefix to avoid collisions. You may specify your prefix here.
The prefix will default to the name of your application class if it isn't set explicitly, which should avoid collisions if you're running multiple applications on the same host.
exceeded
-
The
run_mode
to redirect the client to, when their request-count has exceeded the specified limit.
CALLBACKS
throttle_keys
This callback will be called to give the developer the option to use alternative keys. It must return a list of key value pairs, and the plugin will preserve the order. Default these are remote_user
, remote_addr
, and http_user_agent
.
sub throttle_keys {
remote_user => $ENV{ REMOTE_USER },
remote_addr => $ENV{ REMOTE_ADDR },
http_user_agent => $ENV{ HTTP_USER_AGENT },
}
This callback can be used to do more fancy things and add a key for run-modes as in:
sub throttle_keys {
my $self = shift;
return (
runmode_grp => $self->_get_runmode_group(),
... => ...
)
}
Returning a explicit undef
means that no throttling will happen, at all; If the call back returns an empty list, all incoming request will be throttled and no difference will be made from where the request comes from.
sub throttle_keys {
return undef if $ENV{REMOTE_USER} eq 'superuser';
return ( );
}
throttle_spec
This callback can be used to specify different set of throttle rules based on filters that must match with the throttle keys. This callback must return a list of filter/settings pairs that will be checked against the current throttle keys. It can have a additional last set of throttle rules (it is an odd sized list), which will then be used as a default.
The selected rules willbe merged with the settings from the Cconfigure> call, or the defaults from the module itself.
Keys mentioned in the filter must be present in the current throttle keys/params in order to match. The value can be undef
, meaning that the throttle param must exist and be undefined.
sub throttle_spec {
{ remote_user => undef } =>
{
limit => 5,
exceeded => 'we_dont_like_strangers'
},
{ runmode_grp => 'pdf_report' } =>
{
limit => 10,
period => 3600,
exceeded => 'these_are_very_expensive'
}
{
limit => rnd * 10 # making people go crazy why?
}
}
AUTHOR
Steve Kemp <steve@steve.org.uk>
CONTRIBUTORS
Theo van Hoesel <tvanhoesel@perceptyx.com>
COPYRIGHT AND LICENSE
Copyright (C) 2014..2020 Steve Kemp <steve@steve.org.uk>.
This library is free software. You can modify and or distribute it under the same terms as Perl itself.