The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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.