在向公网提供API供外部访问数据时,为了避免被恶意攻击除了token认证最好还要给API加上请求频次限制,而在Laravel中从5.2开始框架自带的组件Throttle就支持访问频次限制了,并提供了一个Throttle中间件供我们使用,不过Throttle中间件在访问API频次达到限制后会返回一个HTML响应告诉你请求超频,在应用中我们往往更希望返回一个API响应而不是一个HTML响应,所以在文章中会提供一个自定义的中间件替换默认的Throttle中间件来实现自定义响应内容。

访问频次限制概述

频次限制经常用在API中,用于限制独立请求者对特定API的请求频率。例如,如果设置频次限制为每分钟1000次,如果一分钟内超过这个限制,那么服务器就会返回 429: Too Many Attempts.响应。

通常,一个编码良好的、实现了频率限制的应用还会回传三个响应头: X-RateLimit-Limit, X-RateLimit-RemainingRetry-AfterRetry-After头只有在达到限制次数后才会返回)。 X-RateLimit-Limit告诉我们在指定时间内允许的最大请求次数, X-RateLimit-Remaining指的是在指定时间段内剩下的请求次数, Retry-After指的是距离下次重试请求需要等待的时间(s)

注意:每个应用都会选择一个自己的频率限制时间跨度,Laravel应用访问频率限制的时间跨度是一分钟,所以频率限制限制的是一分钟内的访问次数。

使用Throttle中间件

让我们先来看看这个中间件的用法,首先我们定义一个路由,将中间件throttle添加到其中,throttle默认限制每分钟尝试60次,并且在一分钟内访问次数达到60次后禁止访问:

Route::group(['prefix'=>'api','middleware'=>'throttle'], function(){Route::get('users', function(){return \App\User::all();});
});

访问路由/api/users时你会看见响应头里有如下的信息:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58

如果请求超频,响应头里会返回Retry-After:

Retry-After: 58
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0

上面的信息表示58秒后页面或者API的访问才能恢复正常。

定义频率和重试等待时间

频率默认是60次可以通过throttle中间件的第一个参数来指定你想要的频率,重试等待时间默认是一分钟可以通过throttle中间件的第二个参数来指定你想要的分钟数。

Route::group(['prefix'=>'api','middleware'=>'throttle:5'],function(){Route::get('users',function(){return \App\User::all();});
});//频次上限5Route::group(['prefix'=>'api','middleware'=>'throttle:5,10'],function(){Route::get('users',function(){return \App\User::all();});
});//频次上限5,重试等待时间10分钟

自定义Throttle中间件,返回API响应

在请求频次达到上限后Throttle除了返回那些响应头,返回的响应内容是一个HTML页面,页面上告诉我们Too Many Attempts。在调用API的时候我们显然更希望得到一个json响应,下面提供一个自定义的中间件替代默认的Throttle中间件来自定义响应信息。

首先创建一个ThrottleRequests中间件: php artisan make:middleware ThrottleRequests.

将下面的代码拷贝到app/Http/Middlewares/ThrottleReuqests文件中:

<?phpnamespace App\Http\Middleware;use Closure;
use Illuminate\Cache\RateLimiter;
use Symfony\Component\HttpFoundation\Response;class ThrottleRequests
{/*** The rate limiter instance.** @var \Illuminate\Cache\RateLimiter*/protected $limiter;/*** Create a new request throttler.** @param  \Illuminate\Cache\RateLimiter $limiter*/public function __construct(RateLimiter $limiter){$this->limiter = $limiter;}/*** Handle an incoming request.** @param  \Illuminate\Http\Request $request* @param  \Closure $next* @param  int $maxAttempts* @param  int $decayMinutes* @return mixed*/public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1){$key = $this->resolveRequestSignature($request);if ($this->limiter->tooManyAttempts($key, $maxAttempts, $decayMinutes)) {return $this->buildResponse($key, $maxAttempts);}$this->limiter->hit($key, $decayMinutes);$response = $next($request);return $this->addHeaders($response, $maxAttempts,$this->calculateRemainingAttempts($key, $maxAttempts));}/*** Resolve request signature.** @param  \Illuminate\Http\Request $request* @return string*/protected function resolveRequestSignature($request){return $request->fingerprint();}/*** Create a 'too many attempts' response.** @param  string $key* @param  int $maxAttempts* @return \Illuminate\Http\Response*/protected function buildResponse($key, $maxAttempts){$message = json_encode(['error' => ['message' => 'Too many attempts, please slow down the request.' //may comes from lang file],'status_code' => 4029 //your custom code]);$response = new Response($message, 429);$retryAfter = $this->limiter->availableIn($key);return $this->addHeaders($response, $maxAttempts,$this->calculateRemainingAttempts($key, $maxAttempts, $retryAfter),$retryAfter);}/*** Add the limit header information to the given response.** @param  \Symfony\Component\HttpFoundation\Response $response* @param  int $maxAttempts* @param  int $remainingAttempts* @param  int|null $retryAfter* @return \Illuminate\Http\Response*/protected function addHeaders(Response $response, $maxAttempts, $remainingAttempts, $retryAfter = null){$headers = ['X-RateLimit-Limit' => $maxAttempts,'X-RateLimit-Remaining' => $remainingAttempts,];if (!is_null($retryAfter)) {$headers['Retry-After'] = $retryAfter;$headers['Content-Type'] = 'application/json';}$response->headers->add($headers);return $response;}/*** Calculate the number of remaining attempts.** @param  string $key* @param  int $maxAttempts* @param  int|null $retryAfter* @return int*/protected function calculateRemainingAttempts($key, $maxAttempts, $retryAfter = null){if (!is_null($retryAfter)) {return 0;}return $this->limiter->retriesLeft($key, $maxAttempts);}
}

然后将app/Http/Kernel.php文件里的:

'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,

替换成:

'throttle' => \App\Http\Middleware\ThrottleRequests::class,

就大功告成了。

Throttle信息存储

最后再来说下,Throttle这些频次数据都是存储在cache里的,Laravel默认的cache driverfile也就是throttle信息会默认存储在框架的cache文件里, 如果你的cache driver换成redis那么这些信息就会存储在redis里,记录的信息其实很简单,Throttle会将请求对象的signature(以HTTP请求方法、域名、URI和客户端IP做哈希)作为缓存key记录客户端的请求次数。

Laravel最佳实践--API请求频率限制(Throttle中间件)相关推荐

  1. Laravel最佳实践--事件驱动编程

    在这篇文章中我们将了解到什么是"事件驱动编程"以及在Laravel中如何开始构建一个事件驱动应用,同时我们还将看到如何通过事件驱动编程来对应用程序的逻辑进行解耦. 在开始之前,先说 ...

  2. Laravel最佳实践--根据搜索参数为Model查询应用不同的Where条件

    我们平时使用Laravel的Model查询当查询应用了多个条件的时候一般都是链式调用,像下面这样: User::where('age', '=', 18)->where('sex', '=', ...

  3. API接口设计最佳实践

    目录 目录 前言 API接口设计 Token设计 API接口设计原则 1.明确协议规范 2.统一接口路径规范 3.统一接口版本管理 4.为你的接口设定调用门槛 5.接口返回规范 6.接口安全规范 7. ...

  4. 来自Google资深工程师的API设计最佳实践

    来自Google资深工程师Joshua Bloch的分享:API设计最佳实践 为什么API设计如此重要?API是一个公司最重要的资产. 为什么API的设计对程序员如此重要? API一旦发布,出于兼容性 ...

  5. android文件选择器_Android存储空间的最佳实践(上)

    为了提高文件的规整程度并让用户可以更好地控制他们的文件,Android 10 为应用引入了名为 "分区存储" 的新范式.分区存储改变了应用在外置存储中保存和访问文件的方式,为了帮您 ...

  6. 使用 Node.js Express 的最佳实践

    Production best practices: performance and reliability 本文讨论部署到生产的 Express 应用程序的性能和可靠性最佳实践. 这个话题显然属于& ...

  7. Android 应用开发(19)--- 应用权限最佳实践

    应用权限最佳实践 权限请求保护设备提供的敏感信息,并且只有在访问信息对于您的应用的运行是必要的时才应使用.本文档提供了有关如何在不需要访问此类信息的情况下实现相同(或更好)功能的提示; 这并不是关于权 ...

  8. React Api请求最佳实践react-query3使用教程(比swr更好用更强大)

    前言 在请求中,首先 axios 作为请求底层封装库,统一拦截,处理发送请求头和接收的错误响应. 那么更高一层的封装可以选择 swr 或者 react-query,目前 react-query 已经进 ...

  9. API接口安全思考和最佳实践

    一.API安全概述 应用程序编程接口 (API) 允许软件应用程序相互交互.它是现代软件模式的基本组成部分,例如微服务架构. API 安全是保护 API 免受攻击的过程.由于 API 非常常用,而且它 ...

最新文章

  1. HDU4472 Count
  2. shiro+thymeleaf 整合
  3. Asp.Net Core 中的“虚拟目录”
  4. 2560介绍_炒股高手收益翻10倍,只因妙用这一招2560战法,看了都不亏了
  5. xfire客户端对返回list很挑剔,所以需要使用泛型。
  6. 不用图片而用css3实现一些阴影特效
  7. 单个雪碧图多个图像资源你该如何解决它们的定位?
  8. 创建您自己的.NET DynamicObject 为什么、何时和如何
  9. mysql三高讲解(二):2.3 InnoDB索引即数据
  10. 如何安装mysql5.7.15_ubuntu16.04安装mysql5.7.15
  11. 利用反射打印对象一般成员属性值
  12. Oracle DB 优化-AWR及相关内容
  13. 如何在WebGL全景图上做标记
  14. 【.net core 跨平台】第一步 在Ubuntu16.04 配置.net core环境
  15. 数字图像处理--噪声
  16. EP100伺服电机驱动全套资料,STM32平台FOC控制
  17. dh协议c语言代码,openssl开源程序dh算法解析之dh_ameth.c
  18. 主从表mysql外键_主从表主键外键
  19. 让国外买家秒回复的询盘模板示例
  20. 白糖详细 制造工艺、等级划分、国家标准号和注意事项

热门文章

  1. 今天讲讲hibernate的简单使用
  2. 从远程服务器获取数据
  3. 我的第一个wp8小程序
  4. 深入理解 Android Activity的生命周期
  5. msyql开启慢查询以及分析慢查询
  6. 首届渣打科营编程马拉松赛初赛圆满结束
  7. 如何看待阿里云加入Linux基金会金牌会员?
  8. 只“存活”9个月:Ubuntu 15.10今日停止支持
  9. 关于captcha使用The _imagingft C module is not installed的错误处理
  10. 移动语音引擎相关开发笔记