Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yii2
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
PSDI Army
yii2
Commits
12cd71d7
Commit
12cd71d7
authored
Apr 05, 2014
by
Qiang Xue
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactored rate limiting by turning RateLimiter into an action filter.
parent
3616d1f0
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
88 additions
and
62 deletions
+88
-62
rest.md
docs/guide/rest.md
+21
-5
CHANGELOG.md
framework/CHANGELOG.md
+1
-0
classes.php
framework/classes.php
+2
-2
RateLimitInterface.php
framework/filters/RateLimitInterface.php
+10
-7
RateLimiter.php
framework/filters/RateLimiter.php
+50
-14
Controller.php
framework/rest/Controller.php
+4
-34
No files found.
docs/guide/rest.md
View file @
12cd71d7
...
@@ -689,7 +689,7 @@ To prevent abuse, you should consider adding rate limiting to your APIs. For exa
...
@@ -689,7 +689,7 @@ To prevent abuse, you should consider adding rate limiting to your APIs. For exa
of each user to be at most 100 API calls within a period of 10 minutes. If too many requests are received from a user
of each user to be at most 100 API calls within a period of 10 minutes. If too many requests are received from a user
within the period of the time, a response with status code 429 (meaning Too Many Requests) should be returned.
within the period of the time, a response with status code 429 (meaning Too Many Requests) should be returned.
To enable rate limiting, the
[
[yii\web\User::identityClass|user identity class
]
] should implement
[
[yii\
rest
\RateLimitInterface
]
].
To enable rate limiting, the
[
[yii\web\User::identityClass|user identity class
]
] should implement
[
[yii\
filters
\RateLimitInterface
]
].
This interface requires implementation of the following three methods:
This interface requires implementation of the following three methods:
*
`getRateLimit()`
: returns the maximum number of allowed requests and the time period, e.g.,
`[100, 600]`
means
*
`getRateLimit()`
: returns the maximum number of allowed requests and the time period, e.g.,
`[100, 600]`
means
...
@@ -703,17 +703,33 @@ And `loadAllowance()` and `saveAllowance()` can then be implementation by readin
...
@@ -703,17 +703,33 @@ And `loadAllowance()` and `saveAllowance()` can then be implementation by readin
of the two columns corresponding to the current authenticated user. To improve performance, you may also
of the two columns corresponding to the current authenticated user. To improve performance, you may also
consider storing these information in cache or some NoSQL storage.
consider storing these information in cache or some NoSQL storage.
Once the identity class implements the required interface, Yii will automatically use the rate limiter
Once the identity class implements the required interface, Yii will automatically use
[
[yii\filters\RateLimiter
]
]
as specified by
[
[yii\rest\Controller::rateLimiter
]
] to perform rate limiting check. The rate limiter
configured as an action filter for
[
[yii\rest\Controller
]
] to perform rate limiting check. The rate limiter
will thrown a
[
[yii\web\TooManyRequestsHttpException
]
] if rate limit is exceeded.
will thrown a
[
[yii\web\TooManyRequestsHttpException
]
] if rate limit is exceeded. You may configure the rate limiter
as follows in your REST controller classes,
When rate limiting is enabled, every response will be sent with the following HTTP headers containing
```
php
public
function
behaviors
()
{
return
array_merge
(
parent
::
behaviors
(),
[
'rateLimiter'
=>
[
'class'
=>
\yii\filters\RateLimiter
::
className
(),
'enableRateLimitHeaders'
=>
false
,
],
]);
}
```
When rate limiting is enabled, by default every response will be sent with the following HTTP headers containing
the current rate limiting information:
the current rate limiting information:
*
`X-Rate-Limit-Limit`
: The maximum number of requests allowed with a time period;
*
`X-Rate-Limit-Limit`
: The maximum number of requests allowed with a time period;
*
`X-Rate-Limit-Remaining`
: The number of remaining requests in the current time period;
*
`X-Rate-Limit-Remaining`
: The number of remaining requests in the current time period;
*
`X-Rate-Limit-Reset`
: The number of seconds to wait in order to get the maximum number of allowed requests.
*
`X-Rate-Limit-Reset`
: The number of seconds to wait in order to get the maximum number of allowed requests.
You may disable these headers by configuring
[
[yii\filters\RateLimiter::enableRateLimitHeaders
]
] to be false,
like shown in the above code example.
Error Handling
Error Handling
--------------
--------------
...
...
framework/CHANGELOG.md
View file @
12cd71d7
...
@@ -289,6 +289,7 @@ Yii Framework 2 Change Log
...
@@ -289,6 +289,7 @@ Yii Framework 2 Change Log
-
New: Yii framework now comes with core messages in multiple languages
-
New: Yii framework now comes with core messages in multiple languages
-
New: Added
`yii\codeception\DbTestCase`
(qiangxue)
-
New: Added
`yii\codeception\DbTestCase`
(qiangxue)
-
New: Added
`yii\web\GroupUrlRule`
(qiangxue)
-
New: Added
`yii\web\GroupUrlRule`
(qiangxue)
-
New: Added
`yii\filters\RateLimiter`
(qiangxue)
2.
0.0-alpha, December 1, 2013
2.
0.0-alpha, December 1, 2013
-----------------------------
-----------------------------
...
...
framework/classes.php
View file @
12cd71d7
...
@@ -194,8 +194,8 @@ return [
...
@@ -194,8 +194,8 @@ return [
'yii\rest\IndexAction'
=>
YII_PATH
.
'/rest/IndexAction.php'
,
'yii\rest\IndexAction'
=>
YII_PATH
.
'/rest/IndexAction.php'
,
'yii\rest\OptionsAction'
=>
YII_PATH
.
'/rest/OptionsAction.php'
,
'yii\rest\OptionsAction'
=>
YII_PATH
.
'/rest/OptionsAction.php'
,
'yii\rest\QueryParamAuth'
=>
YII_PATH
.
'/rest/QueryParamAuth.php'
,
'yii\rest\QueryParamAuth'
=>
YII_PATH
.
'/rest/QueryParamAuth.php'
,
'yii\
rest\RateLimitInterface'
=>
YII_PATH
.
'/rest
/RateLimitInterface.php'
,
'yii\
filters\RateLimitInterface'
=>
YII_PATH
.
'/filters
/RateLimitInterface.php'
,
'yii\
rest\RateLimiter'
=>
YII_PATH
.
'/rest
/RateLimiter.php'
,
'yii\
filters\RateLimiter'
=>
YII_PATH
.
'/filters
/RateLimiter.php'
,
'yii\rest\Serializer'
=>
YII_PATH
.
'/rest/Serializer.php'
,
'yii\rest\Serializer'
=>
YII_PATH
.
'/rest/Serializer.php'
,
'yii\rest\UpdateAction'
=>
YII_PATH
.
'/rest/UpdateAction.php'
,
'yii\rest\UpdateAction'
=>
YII_PATH
.
'/rest/UpdateAction.php'
,
'yii\rest\UrlRule'
=>
YII_PATH
.
'/rest/UrlRule.php'
,
'yii\rest\UrlRule'
=>
YII_PATH
.
'/rest/UrlRule.php'
,
...
...
framework/
rest
/RateLimitInterface.php
→
framework/
filters
/RateLimitInterface.php
View file @
12cd71d7
...
@@ -5,7 +5,7 @@
...
@@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/
* @license http://www.yiiframework.com/license/
*/
*/
namespace
yii\
rest
;
namespace
yii\
filters
;
/**
/**
* RateLimitInterface is the interface that may be implemented by an identity object to enforce rate limiting.
* RateLimitInterface is the interface that may be implemented by an identity object to enforce rate limiting.
...
@@ -17,23 +17,26 @@ interface RateLimitInterface
...
@@ -17,23 +17,26 @@ interface RateLimitInterface
{
{
/**
/**
* Returns the maximum number of allowed requests and the window size.
* Returns the maximum number of allowed requests and the window size.
* @param array $params the additional parameters associated with the rate limit.
* @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,
* @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.
* and the second element is the size of the window in seconds.
*/
*/
public
function
getRateLimit
(
$
params
=
[]
);
public
function
getRateLimit
(
$
request
,
$action
);
/**
/**
* Loads the number of allowed requests and the corresponding timestamp from a persistent storage.
* 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.
* @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,
* @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.
* and the second element is the corresponding UNIX timestamp.
*/
*/
public
function
loadAllowance
(
$
params
=
[]
);
public
function
loadAllowance
(
$
request
,
$action
);
/**
/**
* Saves the number of allowed requests and the corresponding timestamp to a persistent storage.
* 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 integer $allowance the number of allowed requests remaining.
* @param integer $allowance the number of allowed requests remaining.
* @param integer $timestamp the current timestamp.
* @param integer $timestamp the current timestamp.
* @param array $params the additional parameters associated with the rate limit.
*/
*/
public
function
saveAllowance
(
$
allowance
,
$timestamp
,
$params
=
[]
);
public
function
saveAllowance
(
$
request
,
$action
,
$allowance
,
$timestamp
);
}
}
framework/
rest
/RateLimiter.php
→
framework/
filters
/RateLimiter.php
View file @
12cd71d7
...
@@ -5,9 +5,10 @@
...
@@ -5,9 +5,10 @@
* @license http://www.yiiframework.com/license/
* @license http://www.yiiframework.com/license/
*/
*/
namespace
yii\
rest
;
namespace
yii\
filters
;
use
yii\base\Component
;
use
Yii
;
use
yii\base\ActionFilter
;
use
yii\web\Request
;
use
yii\web\Request
;
use
yii\web\Response
;
use
yii\web\Response
;
use
yii\web\TooManyRequestsHttpException
;
use
yii\web\TooManyRequestsHttpException
;
...
@@ -15,14 +16,35 @@ use yii\web\TooManyRequestsHttpException;
...
@@ -15,14 +16,35 @@ use yii\web\TooManyRequestsHttpException;
/**
/**
* RateLimiter implements a rate limiting algorithm based on the [leaky bucket algorithm](http://en.wikipedia.org/wiki/Leaky_bucket).
* 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.
* You may use RateLimiter by attaching it as a behavior to a controller or module, like the following,
*
* ```php
* public function behaviors()
* {
* return [
* 'rateLimiter' => [
* 'class' => \yii\filters\RateLimiter::className(),
* ],
* ];
* }
* ```
*
* When the user has exceeded his rate limit, RateLimiter will throw a [[TooManyRequestsHttpException]] exception.
*
* Note that RateLimiter requires [[user]] to implement the [[RateLimitInterface]]. RateLimiter will
* do nothing if [[user]] is not set or does not implement [[RateLimitInterface]].
*
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
* @since 2.0
*/
*/
class
RateLimiter
extends
Component
class
RateLimiter
extends
ActionFilter
{
{
/**
/**
* @var RateLimitInterface the user object that implements the RateLimitInterface.
* If not set, it will take the value of `Yii::$app->user->getIdentity(false)`.
*/
public
$user
;
/**
* @var boolean whether to include rate limit headers in the response
* @var boolean whether to include rate limit headers in the response
*/
*/
public
$enableRateLimitHeaders
=
true
;
public
$enableRateLimitHeaders
=
true
;
...
@@ -31,6 +53,24 @@ class RateLimiter extends Component
...
@@ -31,6 +53,24 @@ class RateLimiter extends Component
*/
*/
public
$errorMessage
=
'Rate limit exceeded.'
;
public
$errorMessage
=
'Rate limit exceeded.'
;
/**
* @inheritdoc
*/
public
function
beforeAction
(
$action
)
{
$user
=
$this
->
user
?
:
Yii
::
$app
->
getUser
()
->
getIdentity
(
false
);
if
(
$user
instanceof
RateLimitInterface
)
{
Yii
::
trace
(
'Check rate limit'
,
__METHOD__
);
$this
->
checkRateLimit
(
$user
,
Yii
::
$app
->
getRequest
(),
Yii
::
$app
->
getResponse
(),
$action
);
}
elseif
(
$user
)
{
Yii
::
info
(
'Rate limit skipped: "user" does not implement RateLimitInterface.'
);
}
else
{
Yii
::
info
(
'Rate limit skipped: user not logged in.'
);
}
return
true
;
}
/**
/**
* Checks whether the rate limit exceeds.
* Checks whether the rate limit exceeds.
* @param RateLimitInterface $user the current user
* @param RateLimitInterface $user the current user
...
@@ -39,16 +79,12 @@ class RateLimiter extends Component
...
@@ -39,16 +79,12 @@ class RateLimiter extends Component
* @param \yii\base\Action $action the action to be executed
* @param \yii\base\Action $action the action to be executed
* @throws TooManyRequestsHttpException if rate limit exceeds
* @throws TooManyRequestsHttpException if rate limit exceeds
*/
*/
public
function
check
(
$user
,
$request
,
$response
,
$action
)
public
function
check
RateLimit
(
$user
,
$request
,
$response
,
$action
)
{
{
$current
=
time
();
$current
=
time
();
$params
=
[
'request'
=>
$request
,
'action'
=>
$action
,
];
list
(
$limit
,
$window
)
=
$user
->
getRateLimit
(
$
params
);
list
(
$limit
,
$window
)
=
$user
->
getRateLimit
(
$
request
,
$action
);
list
(
$allowance
,
$timestamp
)
=
$user
->
loadAllowance
(
$
params
);
list
(
$allowance
,
$timestamp
)
=
$user
->
loadAllowance
(
$
request
,
$action
);
$allowance
+=
(
int
)
((
$current
-
$timestamp
)
*
$limit
/
$window
);
$allowance
+=
(
int
)
((
$current
-
$timestamp
)
*
$limit
/
$window
);
if
(
$allowance
>
$limit
)
{
if
(
$allowance
>
$limit
)
{
...
@@ -56,11 +92,11 @@ class RateLimiter extends Component
...
@@ -56,11 +92,11 @@ class RateLimiter extends Component
}
}
if
(
$allowance
<
1
)
{
if
(
$allowance
<
1
)
{
$user
->
saveAllowance
(
0
,
$current
,
$params
);
$user
->
saveAllowance
(
$request
,
$action
,
0
,
$current
);
$this
->
addRateLimitHeaders
(
$response
,
$limit
,
0
,
$window
);
$this
->
addRateLimitHeaders
(
$response
,
$limit
,
0
,
$window
);
throw
new
TooManyRequestsHttpException
(
$this
->
errorMessage
);
throw
new
TooManyRequestsHttpException
(
$this
->
errorMessage
);
}
else
{
}
else
{
$user
->
saveAllowance
(
$
allowance
-
1
,
$current
,
$params
);
$user
->
saveAllowance
(
$
request
,
$action
,
$allowance
-
1
,
$current
);
$this
->
addRateLimitHeaders
(
$response
,
$limit
,
0
,
(
int
)
((
$limit
-
$allowance
)
*
$window
/
$limit
));
$this
->
addRateLimitHeaders
(
$response
,
$limit
,
0
,
(
int
)
((
$limit
-
$allowance
)
*
$window
/
$limit
));
}
}
}
}
...
@@ -72,7 +108,7 @@ class RateLimiter extends Component
...
@@ -72,7 +108,7 @@ class RateLimiter extends Component
* @param integer $remaining the remaining number of allowed requests within the current 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
* @param integer $reset the number of seconds to wait before having maximum number of allowed requests again
*/
*/
p
rotected
function
addRateLimitHeaders
(
$response
,
$limit
,
$remaining
,
$reset
)
p
ublic
function
addRateLimitHeaders
(
$response
,
$limit
,
$remaining
,
$reset
)
{
{
if
(
$this
->
enableRateLimitHeaders
)
{
if
(
$this
->
enableRateLimitHeaders
)
{
$response
->
getHeaders
()
$response
->
getHeaders
()
...
...
framework/rest/Controller.php
View file @
12cd71d7
...
@@ -9,10 +9,10 @@ namespace yii\rest;
...
@@ -9,10 +9,10 @@ namespace yii\rest;
use
Yii
;
use
Yii
;
use
yii\base\InvalidConfigException
;
use
yii\base\InvalidConfigException
;
use
yii\filters\RateLimiter
;
use
yii\web\Response
;
use
yii\web\Response
;
use
yii\web\UnauthorizedHttpException
;
use
yii\web\UnauthorizedHttpException
;
use
yii\web\UnsupportedMediaTypeHttpException
;
use
yii\web\UnsupportedMediaTypeHttpException
;
use
yii\web\TooManyRequestsHttpException
;
use
yii\filters\VerbFilter
;
use
yii\filters\VerbFilter
;
use
yii\web\ForbiddenHttpException
;
use
yii\web\ForbiddenHttpException
;
...
@@ -50,14 +50,6 @@ class Controller extends \yii\web\Controller
...
@@ -50,14 +50,6 @@ class Controller extends \yii\web\Controller
*/
*/
public
$authMethods
;
public
$authMethods
;
/**
/**
* @var string|array the rate limiter class or configuration. If this is not set or empty,
* the rate limiting will be disabled. Note that if the user is not authenticated, the rate limiting
* will also NOT be performed.
* @see checkRateLimit()
* @see authMethods
*/
public
$rateLimiter
=
'yii\rest\RateLimiter'
;
/**
* @var string the chosen API version number, or null if [[supportedVersions]] is empty.
* @var string the chosen API version number, or null if [[supportedVersions]] is empty.
* @see supportedVersions
* @see supportedVersions
*/
*/
...
@@ -88,6 +80,9 @@ class Controller extends \yii\web\Controller
...
@@ -88,6 +80,9 @@ class Controller extends \yii\web\Controller
'class'
=>
VerbFilter
::
className
(),
'class'
=>
VerbFilter
::
className
(),
'actions'
=>
$this
->
verbs
(),
'actions'
=>
$this
->
verbs
(),
],
],
'rateLimiter'
=>
[
'class'
=>
RateLimiter
::
className
(),
],
];
];
}
}
...
@@ -106,7 +101,6 @@ class Controller extends \yii\web\Controller
...
@@ -106,7 +101,6 @@ class Controller extends \yii\web\Controller
public
function
beforeAction
(
$action
)
public
function
beforeAction
(
$action
)
{
{
$this
->
authenticate
(
$action
);
$this
->
authenticate
(
$action
);
$this
->
checkRateLimit
(
$action
);
return
parent
::
beforeAction
(
$action
);
return
parent
::
beforeAction
(
$action
);
}
}
...
@@ -193,30 +187,6 @@ class Controller extends \yii\web\Controller
...
@@ -193,30 +187,6 @@ class Controller extends \yii\web\Controller
}
}
/**
/**
* Ensures the rate limit is not exceeded.
*
* 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
);
}
}
/**
* Serializes the specified data.
* Serializes the specified data.
* The default implementation will create a serializer based on the configuration given by [[serializer]].
* The default implementation will create a serializer based on the configuration given by [[serializer]].
* It then uses the serializer to serialize the given data.
* It then uses the serializer to serialize the given data.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment