Last active
May 7, 2021 00:41
-
-
Save GuilhermeBarile/04c176da68b78d3ab5b997aec3933eba to your computer and use it in GitHub Desktop.
php/redis rate limiter and blacklist handler
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 | |
/** | |
* Based on https://engineering.classdojo.com/blog/2015/02/06/rolling-rate-limiter/ | |
*/ | |
for ($i = 1; $i < 20; $i++) { | |
// limit 5 runs of action by ip every 10 seconds, runs function if overflown | |
limit('action', 'ip', 5, 10, function($count) { | |
echo "Ran limit function after $count runs"; | |
}); | |
} | |
// check if 'user' or 'ip' are banned (FALSE) | |
var_dump(banned(['user', 'ip'])); | |
// ban 'user' | |
ban("user", "misbehave", 10); | |
// check if 'user' or 'ip' are banned ('user' is) | |
var_dump(banned(['user', 'ip'])); | |
// ban 'ip' | |
ban("ip", "ip banned", 10); | |
// check if 'user' or 'ip' are banned (both are) | |
var_dump(banned(['user', 'ip'])); | |
// --- | |
// Functions | |
// --- | |
/** | |
* Singleton, returns a Redis instance | |
* @return Redis | |
*/ | |
function redis() { | |
static $redis; | |
if(!$redis) { | |
$redis = new Redis(); | |
$redis->connect("redis", 6379); | |
} | |
return $redis; | |
} | |
/** | |
* Bans $key for $ttl seconds | |
* @param $key | |
* @param int $ttl | |
*/ | |
function ban($key, $reason = "banned", $ttl = 600) { | |
$redis = redis(); | |
$redis->set("ban:$key", $reason); | |
$redis->expire("ban:$key", $ttl); | |
} | |
/** | |
* Check if any of the $keys are banned | |
* @param array $keys | |
* @return bool|string ban reason or FALSE if not banned | |
*/ | |
function banned(array $keys) { | |
$redis = redis(); | |
$mGet = array_map(function($item) { return "ban:$item"; }, $keys); | |
$reason = implode(",", array_filter($redis->mget($mGet), function($item) { | |
return $item !== FALSE; | |
})); | |
return strlen($reason) ? $reason : FALSE; | |
} | |
/** | |
* Limits $action by $client to $threshold occurrencies within the last $ttl seconds | |
* @param $action | |
* @param $client | |
* @param $threshold | |
* @param int $ttl | |
* @param callable | |
* @return int | |
*/ | |
function limit($action, $client, $threshold, $ttl = 60, $fn = null) { | |
$key = "$action:$client"; | |
$ts = microtime(true) * 10000; | |
$redis = redis(); | |
// expire old entries | |
$redis->zRemRangeByScore($key,0, $ts - $ttl * 10000); | |
// count entries | |
$count = $redis->zCount($key, 0, $ts); | |
if($fn && $count >= $threshold) { | |
return call_user_func($fn, $count); | |
} | |
else { | |
// add new entry | |
$redis->zAdd($key, $ts, $ts); | |
$redis->expire($key, $ttl); | |
return $count + 1; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment