Snippets

Brian Medley A Mojo RateLimte Plugin

Created by Brian Medley
package Mojolicious::Plugin::RateLimit::Mojo::SQLite;

use Mojo::Base -base;

has migration => qq(
    -- 1 up

    CREATE TABLE mojolicious_plugin_ratelimit (
        ip varchar(128),
        hits integer,
        route varchar(512),
        inserted varchar(32),
        updated varchar(32)
    );

    -- 1 down

    DROP TABLE mojolicious_plugin_ratelimit;
);

has sql => sub {{
    del => "DELETE FROM mojolicious_plugin_ratelimit",
    sel_global => "SELECT hits, inserted, updated FROM mojolicious_plugin_ratelimit WHERE route IS NULL AND ip = ?",
    sel_route => "SELECT hits, inserted, updated FROM mojolicious_plugin_ratelimit WHERE route IS ? AND ip = ?",
    ins_global => "INSERT INTO mojolicious_plugin_ratelimit (ip, hits, route, inserted, updated) VALUES (?, 1, NULL, ?, ?)",
    ins_route => "INSERT INTO mojolicious_plugin_ratelimit (ip, hits, route, inserted, updated) VALUES (?, 1, ?, ?, ?)",
    upd_global => "UPDATE mojolicious_plugin_ratelimit SET hits = ?, updated = ? WHERE ip = ? AND route IS NULL",
    upd_route => "UPDATE mojolicious_plugin_ratelimit SET hits = ?, updated = ? WHERE ip = ? AND route = ?",
}};

package Mojolicious::Plugin::RateLimit;

use Mojo::Base 'Mojolicious::Plugin';
use Mojo::Loader qw(load_class);

our $VERSION = '0.01';

sub register {
    my ($self, $app, $conf) = @_;
    
    my $method = $conf->{db_helper};

    my $pkg = __PACKAGE__ . "::" . $conf->{db};

    if (my $e = load_class($pkg)) {
        die ref $e ? "Exception: $e" : "Not found: " . $pkg;
    }

    my $meta = $pkg->new;
    
    my $sql = $conf->{sql} // $meta->sql;
    $app->$method->migrations->from_string($meta->migration)->migrate;
    
    $app->$method->db->query($sql->{del});
    
    $app->hook(around_action => sub {
        my ($next, $c, $action, $last) = @_;
    
        my $ip = $c->tx->remote_address;
        my $db = $app->$method->db;

        my $global = $db->query($sql->{sel_global}, $ip)->hash;

        if (!defined $global) {
            my $now = time;
            $db->query($sql->{ins_global}, $ip, $now, $now);
            $db->query($sql->{ins_route}, $ip, $c->url_for->to_abs, $now, $now);

            return $next->();
        }

        my $route = $db->query($sql->{sel_route}, $ip)->hash;

        my $now = time;
        ++$global->{hits};
        $db->query($sql->{upd_global}, $global->{hits}, $now, $ip)->hash;
        $db->query($sql->{upd_route}, $global->{hits}, $now, $ip)->hash;

        my $whence = $global->{updated} - $global->{inserted};
        if ($whence >= 30) {
            $app->$method->db->query($sql->{del});

            return $next->();
        }

        if (30 <= $global->{hits}) {
            return $c->respond_to(
                json => {json => {error => "Too many requests"}, status => 429},
                html => {text => "Too many requests\n", status => 429},
                any  => {text => "Too many requests\n", status => 429},
            );
        }

        return $next->();
    });
}

1;

__END__

=encoding utf8

=head1 NAME

Mojolicious::Plugin::RateLimit - Mojolicious Plugin

=head1 SYNOPSIS

  # Mojolicious
  $self->plugin('RateLimit');

  # Mojolicious::Lite
  plugin 'RateLimit';

=head1 DESCRIPTION

L<Mojolicious::Plugin::RateLimit> is a L<Mojolicious> plugin.

=head1 METHODS

L<Mojolicious::Plugin::RateLimit> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.

=head2 register

  $plugin->register(Mojolicious->new);

Register plugin in L<Mojolicious> application.

=head1 SEE ALSO

L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.

=cut
#!/opt/perl

use Mojolicious::Lite;

use Mojo::SQLite;

helper db => sub {
    state $sql = Mojo::SQLite->new('sqlite:test.db');
};

plugin 'RateLimit' => { db_helper => "db", db => "Mojo::SQLite" };

get '/' => sub {
    my $c = shift;
    
    $c->render(text => scalar(localtime) . "\n");
};

app->start;

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.