Laravel (Throttle中间件)
Laravel 自带了一个 Throttle
中间件,默认的设置是 1 分钟内请求超过 60 次就会触发这个,然后服务器就会返回 429 Too Many Requests
这个默认配置可以在 app\Http\Kernel.php
中看到
限流原理
- 获取唯一请求来源,进行唯一标识(key)
- 获取该请求请求次数 (hits)
- 判断是否超过最大限制
- 若达到上限,进入5。未达到,则进入6
- 丢出访问次数限制异常,结束请求。
- 首先判断hits 是否达到限制,若未达到,进入7。若达到,进入8。
- hits 进行计数 + 1,更新到缓存中。 若是第一次,则需要 hits = 1(次数), 并添加访问标识 key
(1分钟)到缓存中,以标记请求周期。 - 请求次数已达到上限(hits >= 60),此时需要判断是否在周期范围内(1分钟),若在周期内,进入9;不在周期内,进入10.
- 此时请求处在 “1分钟内请求次数达到60次”,即达到限制,返回 false 。
- 此时请求处在 “不在1分钟内请求次数达到60次”,即不在周期内,需要重新计算周期。
更多参考文档:https://www.cnblogs.com/toughlife/p/10601069.html
自定义返回的类型
Laravel 默认 返回的是一个 429
的 html
页面,做 api
的话这样不太好
我们新建一个中间件,来替换掉原来的中间件
artisan
命令新建一个中间件:php artisan make:middleware ThrottleRequests
编写代码:
继承原来的ThrottleRequests
,稍微修改一下就可以了。
如果限制时间要修改成秒的话, 请查看 :$this->limiter->hit($key,&decayMinutes * 60)
$decayMinutes * 60
,如果 decayMinutes
= 1
的话, 那么就是限制一分钟, 1*60
那么修改成 $this->limiter->hit($key,&decayMinutes)
,就成限制秒了
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Response;
class ThrottleRequests extends \Illuminate\Routing\Middleware\ThrottleRequests
{
public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
{
$key = $this->resolveRequestSignature($request);
$maxAttempts = $this->resolveMaxAttempts($request, $maxAttempts);
if ($this->limiter->tooManyAttempts($key, $maxAttempts)) {
return $this->buildException($key, $maxAttempts);
//throw $this->buildException($key, $maxAttempts);
// 原来的是抛出异常,修改成直接返回
}
//去掉 `* 60` 限制秒级,加上去限制分钟,要限制其他单位,可以自己算的
$this->limiter->hit($key, $decayMinutes);
//$this->limiter->hit($key, $decayMinutes * 60);
$response = $next($request);
return $this->addHeaders(
$response, $maxAttempts,
$this->calculateRemainingAttempts($key, $maxAttempts)
);
}
protected function buildException($key, $maxAttempts)
{
$retryAfter = $this->limiter->availableIn($key);
//要返回的数据
$message = json_encode([
'code' => 429,
'data' => null,
'msg' => '您的请求太频繁,已被限制请求',
'retryAfter' => $retryAfter,
], 320);
$response = new Response($message, 200);
return $this->addHeaders(
$response, $maxAttempts,
$this->calculateRemainingAttempts($key, $maxAttempts, $retryAfter),
$retryAfter
);
}
protected function addHeaders(\Symfony\Component\HttpFoundation\Response $response, $maxAttempts, $remainingAttempts, $retryAfter = null)
{
// 添加 `response` 头 为 `json`
$response->headers->add(
['Content-Type' => 'application/json;charset=utf-8']
);
return parent::addHeaders($response, $maxAttempts, $remainingAttempts, $retryAfter);
}
}