summaryrefslogtreecommitdiff
path: root/lib/Multi/API.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Multi/API.pm')
-rw-r--r--lib/Multi/API.pm40
1 files changed, 35 insertions, 5 deletions
diff --git a/lib/Multi/API.pm b/lib/Multi/API.pm
index 3271e27b..7b36755d 100644
--- a/lib/Multi/API.pm
+++ b/lib/Multi/API.pm
@@ -13,6 +13,7 @@ use POE 'Wheel::SocketFactory', 'Wheel::ReadWrite';
use POE::Filter::VNDBAPI 'encode_filters';
use Digest::SHA 'sha256_hex';
use Encode 'encode_utf8';
+use Time::HiRes 'time'; # important for throttling
# not exported by Socket, taken from netinet/tcp.h (specific to Linux, AFAIK)
@@ -21,6 +22,11 @@ sub TCP_KEEPINTVL { 5 }
sub TCP_KEEPCNT { 6 }
+# Global throttle hash, key = username, value = [ cmd_time, sql_time ]
+# TODO: clean up items in this hash when username isn't connected anymore and throttle times < current time
+my %throttle;
+
+
sub spawn {
my $p = shift;
POE::Session->create(
@@ -37,6 +43,8 @@ sub spawn {
conn_per_ip => 5,
sess_per_user => 3,
tcp_keepalive => [ 120, 60, 3 ], # time, intvl, probes
+ throttle_cmd => [ 2, 30 ], # interval between each command, allowed burst
+ throttle_sql => [ 60, 1 ], # sql time multiplier, allowed burst (in sql time)
@_,
c => {},
},
@@ -247,13 +255,28 @@ sub client_input {
# when we're here, we can assume that $cmd contains a valid command
# and the arguments are syntactically valid
- # login
+ # handle login command
return $_[KERNEL]->yield(login => $c, @$arg) if $cmd eq 'login';
-
return cerr $c, needlogin => 'Not logged in.' if !$c->{username};
- # TODO: throttling
- # get
+ # update throttle array of the current user
+ my $time = time;
+ $_ < $time && ($_ = $time) for @{$c->{throttle}};
+
+ # check for thottle rule violation
+ my @limits = ('cmd', 'sql');
+ for (0..$#limits) {
+ my $threshold = $_[HEAP]{"throttle_$limits[$_]"}[0]*$_[HEAP]{"throttle_$limits[$_]"}[1];
+ return cerr $c, throttled => 'Throttle limit reached.', type => $limits[$_],
+ minwait => int(10*($c->{throttle}[$_]-$time-$threshold))/10+1,
+ fullwait => int(10*($c->{throttle}[$_]-$time))/10+1
+ if $c->{throttle}[$_]-$time > $threshold;
+ }
+
+ # update commands/second throttle
+ $c->{throttle}[0] += $_[HEAP]{throttle_cmd}[0];
+
+ # handle get command
return cerr $c, 'parse', "Unkown command '$cmd'" if $cmd ne 'get';
my $type = shift @$arg;
return cerr $c, 'gettype', "Unknown get type: '$type'" if $type ne 'vn';
@@ -293,8 +316,12 @@ sub login_res { # num, res, [ c, arg ]
my $encrypted = sha256_hex($VNDB::S{global_salt}.encode_utf8($arg->{password}).encode_utf8($res->[0]{salt}));
return cerr $c, auth => "Wrong password for user '$arg->{username}'" if lc($encrypted) ne lc($res->[0]{passwd});
- $c->{wheel}->put(['ok']);
+ # link this connection to the users' throttle array (create this if necessary)
+ $throttle{$arg->{username}} = [ time, time ] if !$throttle{$arg->{username}};
+ $c->{throttle} = $throttle{$arg->{username}};
+
$c->{username} = $arg->{username};
+ $c->{wheel}->put(['ok']);
$_[KERNEL]->yield(log => $c,
'Successful login by %s using client "%s" ver. %s', $arg->{username}, $arg->{client}, $arg->{clientver});
}
@@ -425,6 +452,9 @@ sub get_vn_res {
\@ids, 'get_vn_res', { %$get, type => 'relations' });
}
+ # update sql throttle
+ $get->{c}{throttle}[1] += $get->{time}*$_[HEAP]{throttle_sql}[0];
+
# send and log
delete $_->{latest} for @{$get->{list}};
$num = @{$get->{list}};