频率限制

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

准备

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

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

如何做…

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

  1. <?php
  2. namespace app\controllers;
  3. use yii\rest\ActiveController;
  4. use yii\filters\RateLimiter;
  5. use yii\filters\auth\QueryParamAuth;
  6. class FilmController extends ActiveController
  7. {
  8. public $modelClass = 'app\models\Film';
  9. public function behaviors()
  10. {
  11. $behaviors = parent::behaviors();
  12. $behaviors['authenticator'] = [
  13. 'class' => QueryParamAuth::className(),
  14. ];
  15. $behaviors['rateLimiter'] = [
  16. 'class' => RateLimiter::className(),
  17. 'enableRateLimitHeaders' => true
  18. ];
  19. return $behaviors;
  20. }
  21. }

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

  1. <?php
  2. namespace app\models;
  3. class User extends \yii\base\Object implements \yii\web\IdentityInterface, \yii\filters\RateLimitInterface
  4. {
  5. public $id;
  6. public $username;
  7. public $password;
  8. public $authKey;
  9. public $accessToken;
  10. const RATE_LIMIT_NUMBER = 5;
  11. const RATE_LIMIT_RESET = 60;
  12. // it means that user allowed only 5 requests per one minute
  13. public function getRateLimit($request, $action)
  14. {
  15. return [self::RATE_LIMIT_NUMBER,self::RATE_LIMIT_RESET];
  16. }
  17. public function loadAllowance($request, $action)
  18. {
  19. $userAllowance = UserAllowance::findOne($this->id);
  20. return $userAllowance ?
  21. [$userAllowance->allowed_number_requests,$userAllowance->last_check_time]
  22. :$this->getRateLimit($request, $action);
  23. }
  24. public function saveAllowance($request, $action,$allowance,
  25. $timestamp)
  26. {
  27. $userAllowance = ($allowanceModel =UserAllowance::findOne($this->id)) ?$allowanceModel : new UserAllowance();
  28. $userAllowance->user_id = $this->id;
  29. $userAllowance->last_check_time = $timestamp;
  30. $userAllowance->allowed_number_requests =$allowance;
  31. $userAllowance->save();
  32. }
  33. // other User model methods
  34. }

工作原理…

一旦验证类实现了需要的接口,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数据库或者其它能更快获取和加载数据的存储系统。

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

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

你将会得到如下输出:

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

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

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

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

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

参考

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