Sam Tregar > CGI-Application-Plugin-RateLimit > CGI::Application::Plugin::RateLimit



Annotate this POD (1)

View/Report Bugs
Module Version: 1.0   Source  


CGI::Application::Plugin::RateLimit - limits runmode call rate per user


  use CGI::Application::Plugin::RateLimit;

  sub setup {

    # call this in your setup routine to set
    my $rate_limit = $self->rate_limit();

    # set the database handle to use

    # set the table name to use for storing hits, the default is
    # 'rate_limit_hits'

    # keep people from calling 'send' more often than 5 times in 10
    # minutes and 'list' more often than once every 5 seconds.
    $rate_limit->protected_modes(send => {timeframe => '10m',
                                          max_hits  => 5
                                 list => {timeframe => '5s',
                                          max_hits  => 1

    # you can also protect abstract actions, for example to prevent a
    # flood of failed logins
    $rate_limit->protected_actions(failed_login => {timeframe => '10s',
                                                    max_hits  => 2

    # call this runmode when a violation is detected

    # or, run this callback
    $rate_limit->violation_callback(sub { die(...) });

    # override the default identity function
    $rate_limit->identity_callback(sub { ... });

  # record a hit for an action (not needed for run-modes which are
  # handled automatically)
  $rate_limit->record_hit(action => 'failed_login');

  # check for a violation on an action and handle
  return $self->slow_down_buddy
    if( $rate_limit->check_violation(action => 'failed_login') );

  # revoke the most recent hit for this user, preventing it from
  # counting towards a violation

  # examine the violation in violation_mode or violation_callback:
  $mode   = $rate_limit->violated_mode;
  $action = $rate_limit->violated_action;
  $limits = $rate_limit->violated_limits;


This module provides protection against a user calling a runmode too frequently. A typical use-case might be a contact form that sends email. You'd like to allow your users to send you messages, but thousands of messages from a single user would be a problem.

This module works by maintaining a database of hits to protected runmodes. It then checks this database to determine if a new hit should be allowed based on past activity by the user. The user's identity is, by default, tied to login (via REMOTE_USER) or IP address (via REMOTE_IP) if login info is not available. You may provide your own identity function via the identity_callback() method.

To use this module you must create a table in your database with the following schema (using MySQL-syntax, although other DBs may work as well with minor alterations):

  CREATE TABLE rate_limit_hits (
     user_id   VARCHAR(255)      NOT NULL,
     action    VARCHAR(255)      NOT NULL,
     INDEX (user_id, action, timestamp)

You may feel free to vary the storage-type and size of user_id and action to match your usage. For example, if your identity_callback() always returns an integer you could make user_id an integer column.

This table should be periodically cleared of old data. Anything older than the maximum timeframe being used can be safely deleted.

IMPORTANT NOTE: The protection offered by this module is not perfect. Identifying a user on the internet is very hard and a sophisticated attacker can work around these checks, by switching IPs or automating login creation.


The object returned from calling $self->rate_limit on your CGI::App object supports the following method calls:



Call this to set the database handle the object should use. Must be set in setup().



Call this to determine the table to be used to store and lookup hits. The default is 'rate_limit_hits' if not set. See the DESCRIPTION section for the required table schema.


    $rate_limit->protected_modes(send => {timeframe => '10m',
                                          max_hits  => 5
                                 list => {timeframe => '5s',
                                          max_hits  => 1

Takes a list of key-value pairs describing the modes to protect. Keys are names of run-modes. Values are hashes with the following keys:

  timeframe - the timeframe to be considered for violations.  Values
  must be numbers followed by either 's' for seconds, 'm' for minutes
  or 'h' for hours.

  max_hits - how many hits to allow in the specified timeframe before
  triggering a violation.


    $rate_limit->protected_actions(failed_login => {timeframe => '10s',
                                                    max_hits  => 2

Specifies non-run-mode actions to protect. These are arbitrary keys you can use with record_hit() and check_violation(). Takes the same data-structure as protected_modes().



Call to set a run-mode to call when a violation is triggered. Either this or violation_callback must be set.


    $rate_limit->violation_callback(sub { ... });

Callback to call when a violation is detected. Should either throw an exception or return the run-mode to run. Called with the CGI::App object as its sole parameter.


    $rate_limit->identity_callback(sub { ... });

Call this to provide a customized mechanism for determining the identity of the user. The default is:


You might consider adding in session-ID or a hook to your authentication system if it doesn't use REMOTE_USER. Whatever you write should return a single scalar which is expected to be unique to each user.


  $rate_limit->record_hit(action => 'failed_login');

Record a hit for an arbitrary action. This is not needed for run-mode protection. Takes the action name as an argument, which must match an action registered with protected_actions().


  return $self->slow_down_buddy
    if( $rate_limit->check_violation(action => 'failed_login') );

Checks for a violation of a protected action. This is not needed for run-mode protection. Takes the action name as an argument, which must match an action registered with protected_actions().

Returns 1 if a violation took place, 0 otherwise.



Revokes the last hit for this user. You might use this to prevent validation errors from counting against a user, for example.


  $mode = $rate_limit->violated_mode;

Returns the mode for the last violation, or undef if an action caused the violation.


  $mode = $rate_limit->violated_action;

Returns the action for the last violation, or undef if an action caused the violation.


  $limits = $rate_limit->violated_limits;

Returns the hash-ref passed to protected_actions() or protected_modes() for the violated mode/action.


I've tested this module with MySQL and SQLite. I think it's likely to work with many other databases - please let me know if you try one.


Please send questions and suggestions about this module to the CGI::Application mailing-list. To join the mailing list, simply send a blank message to:


This module is in a public Subversion repository at SourceForge here:


I know of no bugs. If you find one, let me know by filing a report on Failing that, you can email me at Please include the version of the module you're using and small test case demonstrating the problem.


Sam Tregar,


Copyright (C) 2006 by Sam Tregar

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.6 or, at your option, any later version of Perl 5 you may have available.

syntax highlighting: