记录api请求次数 php,Laravel最佳实践--API请求频率限制(Throttle中间件)

本文介绍了如何在 Laravel 中利用内置的 Throttle 中间件进行API请求频率限制,防止恶意攻击。通过自定义Throttle中间件,可以返回自定义的API响应而非默认的HTML页面,当请求超出限制时,返回JSON格式的错误信息,包含状态码和错误消息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在向公网提供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-Remaining和 Retry-After(Retry-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();

});

});//频次上限5

Route::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文件中:

namespace 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 driver是file也就是throttle信息会默认存储在框架的cache文件里, 如果你的cache driver换成redis那么这些信息就会存储在redis里,记录的信息其实很简单,Throttle会将请求对象的signature(以HTTP请求方法、域名、URI和客户端IP做哈希)作为缓存key记录客户端的请求次数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值