Skip to content

Instantly share code, notes, and snippets.

@vilkoz
Forked from demisang/ApiRateLimiter.php
Created August 10, 2022 15:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vilkoz/ea67bcc106e1fb18f625fe790766b05e to your computer and use it in GitHub Desktop.
Save vilkoz/ea67bcc106e1fb18f625fe790766b05e to your computer and use it in GitHub Desktop.
Yii2 Api RateLimiter class with Redis storage implementation
<?php
namespace common\components\rateLimiter;
use Yii;
use yii\base\BaseObject;
use yii\filters\RateLimitInterface;
/**
* API rate limiter.
*
* 1. Install & Configure https://github.com/yiisoft/yii2-redis
* 2. Add in controller:
*
* public function behaviors()
* {
* return ArrayHelper::merge(
* parent::behaviors(), [
* 'rateLimiter' => [
* 'class' => \yii\filters\RateLimiter::className(),
* 'enableRateLimitHeaders' => true,
* 'user' => new ApiRateLimiter([
* 'routes' => [
* // route => [maxRequests, perPeriod]
* 'site/index' => [60, 30],
* ],
* ]),
* 'only' => ['only-this-action-need-limitation'],
* 'except' => ['not-limited-action-name'],
* ],
* ]);
* }
*/
class ApiRateLimiter extends BaseObject implements RateLimitInterface
{
public array $routes = [];
public $maxRequestsPerPeriod = 30;
public $period = 60;
protected $limitReached = false;
/**
* Set max requests and period variables depend on current route
*
* @param \yii\base\Action $action
*/
public function setLimitsByRoute($action)
{
if (isset($this->routes[$action->controller->route])) {
$route = $this->routes[$action->controller->route];
$this->maxRequestsPerPeriod = $route[0];
$this->period = $route[1];
}
}
/**
* Returns the maximum number of allowed requests and the window size.
*
* @param \yii\web\Request $request the current request
* @param \yii\base\Action $action the action to be executed
*
* @return array an array of two elements. The first element is the maximum number of allowed requests,
* and the second element is the size of the window in seconds.
*/
public function getRateLimit($request, $action)
{
$this->setLimitsByRoute($action);
// [$this->maxRequestsPerPeriod] times per [$this->period]
return [$this->maxRequestsPerPeriod, $this->period];
}
/**
* Loads the number of allowed requests and the corresponding timestamp from a persistent storage.
*
* @param \yii\web\Request $request the current request
* @param \yii\base\Action $action the action to be executed
*
* @return array an array of two elements. The first element is the number of allowed requests,
* and the second element is the corresponding UNIX timestamp.
*/
public function loadAllowance($request, $action)
{
$redis = Yii::$app->redis;
// Redis storage key
$key = $this->getRedisKey($action, $request->userIP);
// Minimal actual timestamp value
$time = time();
$since = $time - $this->period;
// Begin commands queue
$redis->multi();
// Remove expired values
$redis->zremrangebyscore($key, 0, $since - 1);
// Get requests count by period
$redis->zcount($key, $since, $time);
// Execute commands queue
$result = $redis->exec();
// If error - decline request
if (!is_array($result) || !isset($result[1])) {
return [0, $time];
}
// Count value
$count = (int)$result[1];
if ($count + 1 > $this->maxRequestsPerPeriod) {
$this->limitReached = true;
}
return [$this->maxRequestsPerPeriod - $count, $time];
}
/**
* Saves the number of allowed requests and the corresponding timestamp to a persistent storage.
*
* @param \yii\web\Request $request the current request
* @param \yii\base\Action $action the action to be executed
* @param int $allowance the number of allowed requests remaining.
* @param int $timestamp the current timestamp.
*/
public function saveAllowance($request, $action, $allowance, $timestamp)
{
if ($this->limitReached) {
$this->limitReached = false;
// Don't save "Too many requests" request
return;
}
$redis = Yii::$app->redis;
// Redis storage key
$key = $this->getRedisKey($action, $request->userIP);
// Begin commands queue
$redis->multi();
// Sorted set member score is current timestamp
$score = $timestamp;
// Get random unique value for sorted set member value
$member = uniqid(mt_rand(0, 9));
$redis->zadd($key, $score, $member);
// Set expire for key
$redis->expire($key, $this->period);
// Execute commands queue
$redis->exec();
}
/**
* Get request redis sorted set key
*
* @param \yii\base\Action $action
* @param string $userIp
*
* @return string
*/
protected function getRedisKey($action, $userIp)
{
return md5('limiter:' . $action->controller->route . ':' . $userIp);
}
}
@magiioo
Copy link

magiioo commented Dec 27, 2022

bro please if u have yt channel send me the link

@vilkoz
Copy link
Author

vilkoz commented Dec 29, 2022

@magiioo why?

@vilkoz
Copy link
Author

vilkoz commented Apr 5, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment