Skip to content

Instantly share code, notes, and snippets.

@GuilhermeBarile
Last active May 7, 2021 00:41
Show Gist options
  • Save GuilhermeBarile/04c176da68b78d3ab5b997aec3933eba to your computer and use it in GitHub Desktop.
Save GuilhermeBarile/04c176da68b78d3ab5b997aec3933eba to your computer and use it in GitHub Desktop.
php/redis rate limiter and blacklist handler
<?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