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

摘要:
访问频率限制概述API中经常使用频率限制来限制独立请求者对特定API的请求频率。路由::组;//频率上限5路由::group//最大频率为5。重试等待时间为10分钟。自定义Throttle中间件。在请求频率达到最大值后,Throttle只返回那些响应标头。返回的响应内容是一个HTML页面,它告诉我们TooManyAttempts。当调用API时,我们显然更希望得到一个json响应。接下来,我们提供了一个定制的中间件来替代默认的Throttle中间件来定制响应信息。

在向公网提供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 AppUser::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 AppUser::all();
    });
});//频次上限5

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

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

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

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

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

<?php

namespace AppHttpMiddleware;

use Closure;
use IlluminateCacheRateLimiter;
use SymfonyComponentHttpFoundationResponse;

class ThrottleRequests
{
    /**
     * The rate limiter instance.
     *
     * @var IlluminateCacheRateLimiter
     */
    protected $limiter;

    /**
     * Create a new request throttler.
     *
     * @param  IlluminateCacheRateLimiter $limiter
     */
    public function __construct(RateLimiter $limiter)
    {
        $this->limiter = $limiter;
    }

    /**
     * Handle an incoming request.
     *
     * @param  IlluminateHttpRequest $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  IlluminateHttpRequest $request
     * @return string
     */
    protected function resolveRequestSignature($request)
    {
        return $request->fingerprint();
    }

    /**
     * Create a 'too many attempts' response.
     *
     * @param  string $key
     * @param  int $maxAttempts
     * @return IlluminateHttpResponse
     */
    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  SymfonyComponentHttpFoundationResponse $response
     * @param  int $maxAttempts
     * @param  int $remainingAttempts
     * @param  int|null $retryAfter
     * @return IlluminateHttpResponse
     */
    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' => IlluminateRoutingMiddlewareThrottleRequests::class,

替换成:

throttle' => AppHttpMiddlewareThrottleRequests::class,

就大功告成了。

Throttle信息存储

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

免责声明:文章转载自《Laravel最佳实践--API请求频率限制(Throttle中间件)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Centos7中在线/离线安装DockerCE最新版Linux下解压缩文件命令总结下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

ASP.NET MVC 提供与访问 Web Api

ASP.NET MVC 提供与访问 Web Api 一、提供一个 Web Api 新建一个项目,类型就选 “Web Api”。我用的是MVC5,结果生成的项目一大堆东西,还编译不过,真操蛋。用nuget装了好一阵才跑通。我估计MVC Web项目也可以的,甚至Web Form应该都行。 下面是一个Action。主要是想返回json数据。 public C...

【转】Asp.Net MVC及Web API框架配置会碰到的几个问题及解决方案

前言 刚开始创建MVC与Web API的混合项目时,碰到好多问题,今天拿出来跟大家一起分享下。有朋友私信我问项目的分层及文件夹结构在我的第一篇博客中没说清楚,那么接下来我就准备从这些文件怎么分文件夹说起。问题大概有以下几点: 1、项目层的文件夹结构       2、解决MVC的Controller和Web API的Controller类名不能相同的问题  ...

如何学好VC和MFC(各前辈学习方法及感受整理)(三)

如何学习vc++(vc的用处) 1 vc的用处我感觉下面一些领域比较适合于用vc: 操作系统编程,game,图形设计,corba编程,com编程,网络编程。我谈谈我的体会。我现在在深圳的一家公司从事IP电话网关的开发。通过这个项目,我 才真正体会到VC有多么强大,上百个线程同时工作,数据库并发访问,而且全部软件基 于COM构造,这样高性能的大型软件,只有...

Java全家桶的这些知识,不用学了

众所周知,Java 的知识体系繁冗复杂,但是有很多知识在实际工作中几乎没有人用。 很多人在学习过程中,却经常把有限的时间和精力花在了这些“没有用”的知识上,事倍功半。 下面我捋一捋 Java 中那些不建议学习的知识点,让大家能避过雷区,尽量提升些学习的精准度。 Java 的桌面 GUI 相关技术 GUI,即 Graphical User Interface...

springboot结合jwt实现基于restful接口的身份认证

基于restful接口的身份认证,可以采用jwt的方式实现,想了解jwt,可以查询相关资料,这里不做介绍~ 下面直接看如何实现 1、首先添加jwt的jar包,pom.xml中添加依赖包: <dependency> <groupId>io.jsonwebtoken</groupId>...

(转)创建WebAPI文档的3个简单步骤

默认情况下,Microsoft.AspNet.WebApi.HelpPage创建的API帮助页不包含任何文档。 我们会看到所有的ApiController动作都列在No documentation available.上 要启用文档,我们只需要遵循三个简单步骤。 步骤1-在控制器级别上 为了测试目的,创建了一个新的ApiController,名为Docum...