View on GitHub

yii2-cookbook-chinese

Yii Application Development Cookbook(Third Edition)中文翻译

频率限制

为了防止滥用,你应该考虑为API添加频率限制。例如,你希望每一个用户在一分钟的时间内,最多调用五次API。如果调用过多,将会返回状态码429(太多的请求)。

准备

重复创建一个REST服务器小节中准备如何做…的所有步骤。

  1. 创建一个migration,用于创建一个用户允许表,命令如下:
./yii migrate/create create_user_allowance_table
  1. 然后使用如下代码更新刚刚创建的migration,up
public function up()
{
    $tableOptions = null;
    if ($this->db->driverName === 'mysql') {
        $tableOptions = 'CHARACTER SET utf8 COLLATEutf8_general_ci ENGINE=InnoDB';
    }
    $this->createTable('', [
        'user_id' => $this->primaryKey(),
        'allowed_number_requests' => $this->integer(10)->notNull(),
        'last_check_time' => $this->integer(10)->notNull()
    ], $tableOptions);
}
  1. 使用如下代码更新down方法:
public function down()
{
    $this->dropTable('');
}
  1. 运行创建的create_firm_table migration。
  2. 使用Gii模块生成UserAllowance模型。

如何做…

首先,你必须更新@app/controllers/FilmController.php

<?php
namespace app\controllers;
use yii\rest\ActiveController;
use yii\filters\RateLimiter;
use yii\filters\auth\QueryParamAuth;
class FilmController extends ActiveController
{
    public $modelClass = 'app\models\Film';
    public function behaviors()
    {
        $behaviors = parent::behaviors();
        $behaviors['authenticator'] = [
            'class' => QueryParamAuth::className(),
        ];
        $behaviors['rateLimiter'] = [
            'class' => RateLimiter::className(),
            'enableRateLimitHeaders' => true
        ];
        return $behaviors;
    }
}

为了激活频率限制,User模型类应该实现yii\filters\RateLimitInterface,并实现三个方法:getRateLimit()loadAllowance()saveAllowance()。你需要添加RATE_LIMIT_NUMBERRATE_LIMIT_RESET两个常数:

<?php
namespace app\models;
class User extends \yii\base\Object implements \yii\web\IdentityInterface, \yii\filters\RateLimitInterface
{
    public $id;
    public $username;
    public $password;
    public $authKey;
    public $accessToken;
    const RATE_LIMIT_NUMBER = 5;
    const RATE_LIMIT_RESET = 60;
    // it means that user allowed only 5 requests per one minute
    public function getRateLimit($request, $action)
    {
        return [self::RATE_LIMIT_NUMBER,self::RATE_LIMIT_RESET];
    }
    public function loadAllowance($request, $action)
    {
        $userAllowance = UserAllowance::findOne($this->id);
        return $userAllowance ?
            [$userAllowance->allowed_number_requests,$userAllowance->last_check_time] 
            :$this->getRateLimit($request, $action);
}
    public function saveAllowance($request, $action,$allowance,
                                  $timestamp)
    {
        $userAllowance = ($allowanceModel =UserAllowance::findOne($this->id)) ?$allowanceModel : new UserAllowance();
        $userAllowance->user_id = $this->id;
        $userAllowance->last_check_time = $timestamp;
        $userAllowance->allowed_number_requests =$allowance;
        $userAllowance->save();
    }
    // other User model methods
}

工作原理…

一旦验证类实现了需要的接口,Yii将会自动使用[[yii\rest\Controller]]动作过滤器中配置的[[\yii\filter\RateLimiter]]来运行频率检查。我们也需要为authenticator行为添加QueryParamAuth类。所以,我们现在能够只需要通过查询参数传递一个access token来进行身份校验。你可以添加官方文档中RESTful web服务部分中描述的任何一个身份验证方法。

下面来解释我们的方法,非常容易理解。

getRateLimit():返回允许请求的最大数量和时间段(例如,[100,600]表示600秒内最多运行100次API调用) loadAllowance():返回剩余运行请求的数量,以及上次检查频率限制的UNIX时间戳。 saveAllowance():保存剩余运行请求数量的值,以及当前UNIX时间戳。

我们将数据存放在MySQL数据库中。考虑到性能,你也许可以使用一个NoSQL数据库或者其它能更快获取和加载数据的存储系统。

现在我们来检查频率限制特性。运行如下代码:

curl -i "http://yii-book.app/films?access-token=100-token"

你将会得到如下输出:

HTTP/1.1 200 OK
Date: Thu, 24 Sep 2015 01:35:51 GMT
Server: Apache
X-Powered-By: PHP/5.5.23
Set-Cookie: PHPSESSID=495a928978cc732bee853b83f521eba2; path=/;
HttpOnly
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0,
pre-check=0
Pragma: no-cache
X-Rate-Limit-Limit: 5
X-Rate-Limit-Remaining: 4
X-Rate-Limit-Reset: 0
X-Pagination-Total-Count: 5
X-Pagination-Page-Count: 1
X-Pagination-Current-Page: 1
X-Pagination-Per-Page: 20
Link: <http://yii-book.app/films?access-token=100-token&page=1>;
rel=self
Content-Length: 301
Content-Type: application/json; charset=UTF-8
[{"id":1,"title":"Interstellar","release_year":2014},{"id":2,"title":
"Harry Potter and the Philosopher's
Stone","release_year":2001},{"id":3,"title":"Back to the
Future","release_year":1985},{"id":4,"title":"Blade
Runner","release_year":1982},{"id":5,"title":"Dallas Buyers
Club","release_year":2013}]

来看下返回的头部。当激活了频率限制,默认情况下每次响应将会发送当前频率限制的信息:

X-Rate-Limit-Limit:在一段时间内最大允许请求的数量 X-Rate-Limit-Remaining:在当前时间内容剩余的允许请求数量 X-Rate-Limit-Reset:这是需要等待的时间数,来获取最大数量的允许请求

现在尝试超过限制,在一分钟内运行超过5次,将会得到TooManyRequestsHttpExeption

HTTP/1.1 429 Too Many Requests
Date: Thu, 24 Sep 2015 01:37:24 GMT
Server: Apache
X-Powered-By: PHP/5.5.23
Set-Cookie: PHPSESSID=bb630ca8a641ef92bd210c0a936e3149; path=/;
HttpOnly
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0,
pre-check=0
Pragma: no-cache
X-Rate-Limit-Limit: 5
X-Rate-Limit-Remaining: 0
X-Rate-Limit-Reset: 60
Content-Length: 131
Content-Type: application/json; charset=UTF-8
{"name":"Too Many Requests","message":"Rate limit
exceeded.","code":0,"status":429,"type":"yii\\web\\TooManyRequestsHtt
pException"}

参考

欲了解更多信息,参考如下地址: