mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-20 16:38:22 +08:00
Implemented rate limiter.
This commit is contained in:
@@ -48,6 +48,12 @@ class Controller extends \yii\web\Controller
|
||||
* authentication methods, each represented by an authentication class or configuration.
|
||||
*/
|
||||
public $authMethods = ['yii\rest\HttpBasicAuth', 'yii\rest\HttpBearerAuth', 'yii\rest\QueryParamAuth'];
|
||||
/**
|
||||
* @var string|array the rate limiter class or configuration. If this is not set or empty,
|
||||
* the rate limiting will be disabled.
|
||||
* @see checkRateLimit()
|
||||
*/
|
||||
public $rateLimiter = 'yii\rest\RateLimiter';
|
||||
/**
|
||||
* @var string the chosen API version number
|
||||
* @see supportedVersions
|
||||
@@ -186,15 +192,26 @@ class Controller extends \yii\web\Controller
|
||||
|
||||
/**
|
||||
* Ensures the rate limit is not exceeded.
|
||||
* You may override this method to log the API usage and make sure the rate limit is not exceeded.
|
||||
* If exceeded, you should throw a [[TooManyRequestsHttpException]], and you may also send some HTTP headers,
|
||||
* such as `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`,
|
||||
* to explain the rate limit information.
|
||||
*
|
||||
* This method will use [[rateLimiter]] to check rate limit. In order to perform rate limiting check,
|
||||
* the user must be authenticated and the user identity object (`Yii::$app->user->identity`) must
|
||||
* implement [[RateLimitInterface]].
|
||||
*
|
||||
* @param \yii\base\Action $action the action to be executed
|
||||
* @throws TooManyRequestsHttpException if the rate limit is exceeded.
|
||||
*/
|
||||
protected function checkRateLimit($action)
|
||||
{
|
||||
if (empty($this->rateLimiter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$identity = Yii::$app->getUser()->getIdentity(false);
|
||||
if ($identity instanceof RateLimitInterface) {
|
||||
/** @var RateLimiter $rateLimiter */
|
||||
$rateLimiter = Yii::createObject($this->rateLimiter);
|
||||
$rateLimiter->check($identity, Yii::$app->getRequest(), Yii::$app->getResponse(), $action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
39
framework/rest/RateLimitInterface.php
Normal file
39
framework/rest/RateLimitInterface.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* @link http://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license http://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\rest;
|
||||
|
||||
/**
|
||||
* RateLimitInterface is the interface that may be implemented by an identity object to enforce rate limiting.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
interface RateLimitInterface
|
||||
{
|
||||
/**
|
||||
* Returns the maximum number of allowed requests and the window size.
|
||||
* @param array $params the additional parameters associated with the rate limit.
|
||||
* @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($params = []);
|
||||
/**
|
||||
* Loads the number of allowed requests and the corresponding timestamp from a persistent storage.
|
||||
* @param array $params the additional parameters associated with the rate limit.
|
||||
* @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($params = []);
|
||||
/**
|
||||
* Saves the number of allowed requests and the corresponding timestamp to a persistent storage.
|
||||
* @param integer $allowance the number of allowed requests remaining.
|
||||
* @param integer $timestamp the current timestamp.
|
||||
* @param array $params the additional parameters associated with the rate limit.
|
||||
*/
|
||||
public function saveAllowance($allowance, $timestamp, $params = []);
|
||||
}
|
||||
85
framework/rest/RateLimiter.php
Normal file
85
framework/rest/RateLimiter.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* @link http://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license http://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\rest;
|
||||
|
||||
use yii\base\Component;
|
||||
use yii\base\Action;
|
||||
use yii\web\Request;
|
||||
use yii\web\Response;
|
||||
use yii\web\TooManyRequestsHttpException;
|
||||
|
||||
/**
|
||||
* RateLimiter implements a rate limiting algorithm based on the [leaky bucket algorithm](http://en.wikipedia.org/wiki/Leaky_bucket).
|
||||
*
|
||||
* You may call [[check()]] to enforce rate limiting.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class RateLimiter extends Component
|
||||
{
|
||||
/**
|
||||
* @var boolean whether to include rate limit headers in the response
|
||||
*/
|
||||
public $enableRateLimitHeaders = true;
|
||||
/**
|
||||
* @var string the message to be displayed when rate limit exceeds
|
||||
*/
|
||||
public $errorMessage = 'Rate limit exceeded.';
|
||||
|
||||
/**
|
||||
* Checks whether the rate limit exceeds.
|
||||
* @param RateLimitInterface $user the current user
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param Action $action the action to be executed
|
||||
* @throws TooManyRequestsHttpException if rate limit exceeds
|
||||
*/
|
||||
public function check($user, $request, $response, $action)
|
||||
{
|
||||
$current = time();
|
||||
$params = [
|
||||
'request' => $request,
|
||||
'action' => $action,
|
||||
];
|
||||
|
||||
list ($limit, $window) = $user->getRateLimit($params);
|
||||
list ($allowance, $timestamp) = $user->loadAllowance($params);
|
||||
|
||||
$allowance += (int)(($current - $timestamp) * $limit / $window);
|
||||
if ($allowance > $limit) {
|
||||
$allowance = $limit;
|
||||
}
|
||||
|
||||
if ($allowance < 1) {
|
||||
$user->saveAllowance(0, $current, $params);
|
||||
$this->addRateLimitHeaders($response, $limit, 0, $window);
|
||||
throw new TooManyRequestsHttpException($this->errorMessage);
|
||||
} else {
|
||||
$user->saveAllowance($allowance - 1, $current, $params);
|
||||
$this->addRateLimitHeaders($response, $limit, 0, (int)(($limit - $allowance) * $window / $limit));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the rate limit headers to the response
|
||||
* @param Response $response
|
||||
* @param integer $limit the maximum number of allowed requests during a period
|
||||
* @param integer $remaining the remaining number of allowed requests within the current period
|
||||
* @param integer $reset the number of seconds to wait before having maximum number of allowed requests again
|
||||
*/
|
||||
protected function addRateLimitHeaders($response, $limit, $remaining, $reset)
|
||||
{
|
||||
if ($this->enableRateLimitHeaders) {
|
||||
$response->getHeaders()
|
||||
->set('X-Rate-Limit-Limit', $limit)
|
||||
->set('X-Rate-Limit-Remaining', $remaining)
|
||||
->set('X-Rate-Limit-Reset', $reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user