Last active
December 16, 2019 21:34
-
-
Save RobThree/7786872 to your computer and use it in GitHub Desktop.
Simple, rude, rate limiter (using memcache).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* Very simple, crude, rate limiter */ | |
/* | |
Assumes _MEMCACHEDSITEPREFIX, _MEMCACHEDHOST and _MEMCACHEDPORT are `defined`: | |
define('_MEMCACHEDHOST', '127.0.0.1'); | |
define('_MEMCACHEDPORT', 11211); | |
define('_MEMCACHEDSITEPREFIX', 'mysite'); | |
*/ | |
class RateLimiter { | |
private $rate_limit_cycle; //Seconds | |
private $cycle_req_quota; //Reqs/cycle | |
function RateLimiter($rate_limit_cycle = 86400, $cycle_req_quota = 5000) { | |
$this->rate_limit_cycle = $rate_limit_cycle; | |
$this->cycle_req_quota = $cycle_req_quota; | |
} | |
public function IsLimited($key, $prolong_on_rate_limit_exceeded = false) { | |
$memcache_id = _MEMCACHEDSITEPREFIX . '.ratelimit.' . $key; | |
$memcache = new Memcache(); | |
$memcache->connect(_MEMCACHEDHOST, _MEMCACHEDPORT); | |
$current_hits = intval($memcache->get($memcache_id)); | |
if ($current_hits >= $this->cycle_req_quota) { | |
if ($prolong_on_rate_limit_exceeded) { | |
//When rate limit is hit we prolong the key to the rate_limit_cycle number of seconds ("extra punishment") | |
$memcache->set($memcache_id, $this->cycle_req_quota, 0, $this->rate_limit_cycle); | |
} | |
return true; | |
} | |
if ($current_hits == 0) | |
$memcache->set($memcache_id, 1, 0, $this->rate_limit_cycle); | |
else | |
$memcache->increment($memcache_id); | |
return false; | |
} | |
//Returns if the request is rate limited; returning an error etc. is up to you | |
public static function CheckLimit($key, $rate_limit_cycle = 86400, $cycle_req_quota = 5000, $prolong_on_rate_limit_exceeded = false) { | |
$r = new RateLimiter($rate_limit_cycle, $cycle_req_quota, $prolong_on_rate_limit_exceeded); | |
return $r->IsLimited($key); | |
} | |
//Checks the rate limit for a specific key and exits with some HTTP headers when the rate limit is hit | |
public static function CheckLimitExit($key, $rate_limit_cycle = 86400, $cycle_req_quota = 5000, $prolong_on_rate_limit_exceeded = false) { | |
if (RateLimiter::CheckLimit($key, $rate_limit_cycle, $cycle_req_quota, $prolong_on_rate_limit_exceeded)) { | |
header('HTTP/1.1 429 Too Many Requests'); | |
header(sprintf('Retry-After: %d', $rate_limit_cycle)); | |
header('Connection: close'); | |
echo '<h1>Too many requests</h1>'; | |
exit(); | |
} | |
} | |
//RateLimiter::CheckLimitByIPExit(); //5000 requests per day | |
//RateLimiter::CheckLimitByIPExit(null, 10, 5); //5 requests per 10 seconds | |
//RateLimiter::CheckLimitByIPExit("somemethodname", 10, 5); //Same as above, this time for a specific method | |
public static function CheckLimitByIPExit($key = null, $rate_limit_cycle = 86400, $cycle_req_quota = 5000, $prolong_on_rate_limit_exceeded = false) { | |
RateLimiter::CheckLimitExit((isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null) . $key, $rate_limit_cycle, $cycle_req_quota, $prolong_on_rate_limit_exceeded); | |
} | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Bet you didn't expect a comment on this! I just used a lightly modified version of this snippet in a project :)
Turns out that the value of $prolong_on_rate_limit_exceeded is not being respected. The static function CheckLimit() is creating a new RateLimiter and passing in $prolong_on_rate_limit_exceeded as a 3rd argument, but the constructor doesn't take a 3rd argument. As written, $prolong_on_rate_limit_exceeded is expected to be passed into IsLimited(), but it is not.
What I did was to add $prolong_on_rate_limit_exceeded as an optional parameter to the constructer, like it is with $rate_limit_cycle and $cycle_req_quota:
I then changed IsLimited()'s declaration to remove the $prolong_on_rate_limit_exceeded argument, and the function body to reference the class variable instead:
$this->prolong_on_rate_limit_exceeded
.Thanks for posting this 😃