lumen自定义封装api限流中间件

背景

现在公司重构api项目,针对有些写入和请求的接口需要进行限制设置。比如说一分钟60次等。看了网上的都是laravel的throttle限流,但是没有针对lumen的,所以需要自己重新封装。

实现

  • 1.在App\Http\Middleware下创建一个自定义的中间件,名为ThrottleRequestsMiddleware。
    在这里插入图片描述
  • 2 将如下自定义的中间件代码复制粘贴到中间件内。
    备注:这里代码已经封装改写好了,复制粘贴既可以用
<?php

namespace App\Http\Middleware;

use App\Exceptions\ThrottleException;
use Closure;
use Illuminate\Cache\RateLimiter;
use Illuminate\Cache\RateLimiting\Unlimited;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Illuminate\Support\Arr;
use Illuminate\Support\InteractsWithTime;
use RuntimeException;
use Symfony\Component\HttpFoundation\Response;

class ThrottleRequestsMiddleware
{
   use InteractsWithTime;

   /**
    * The rate limiter instance.
    *
    * @var \Illuminate\Cache\RateLimiter
    */
   protected $limiter;

   /**
    * Indicates if the rate limiter keys should be hashed.
    *
    * @var bool
    */
   protected static $shouldHashKeys = true;

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

   /**
    * Specify the named rate limiter to use for the middleware.
    *
    * @param  string  $name
    * @return string
    */
   public static function using($name)
   {
       return static::class.':'.$name;
   }

   /**
    * Specify the rate limiter configuration for the middleware.
    *
    * @param  int  $maxAttempts
    * @param  int  $decayMinutes
    * @param  string  $prefix
    * @return string
    *
    * @named-arguments-supported
    */
   public static function with($maxAttempts = 60, $decayMinutes = 1, $prefix = '')
   {
       return static::class.':'.implode(',', func_get_args());
   }

   /**
    * Handle an incoming request.
    *
    * @param  \Illuminate\Http\Request  $request
    * @param  \Closure  $next
    * @param  int|string  $maxAttempts
    * @param  float|int  $decayMinutes
    * @param  string  $prefix
    * @return \Symfony\Component\HttpFoundation\Response
    *
    * @throws \Illuminate\Http\Exceptions\ThrottleRequestsException
    */
   public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1, $prefix = '')
   {
       if (is_string($maxAttempts)
           && func_num_args() === 3
           && ! is_null($limiter = $this->limiter->limiter($maxAttempts))) {
           return $this->handleRequestUsingNamedLimiter($request, $next, $maxAttempts, $limiter);
       }

       return $this->handleRequest(
           $request,
           $next,
           [
               (object) [
                   'key' => $prefix.$this->resolveRequestSignature($request),
                   'maxAttempts' => $this->resolveMaxAttempts($request, $maxAttempts),
                   'decaySeconds' => 60 * $decayMinutes,
                   'responseCallback' => null,
               ],
           ]
       );
   }

   /**
    * Handle an incoming request.
    *
    * @param  \Illuminate\Http\Request  $request
    * @param  \Closure  $next
    * @param  string  $limiterName
    * @param  \Closure  $limiter
    * @return \Symfony\Component\HttpFoundation\Response
    *
    * @throws \Illuminate\Http\Exceptions\ThrottleRequestsException
    */
   protected function handleRequestUsingNamedLimiter($request, Closure $next, $limiterName, Closure $limiter)
   {
       $limiterResponse = $limiter($request);

       if ($limiterResponse instanceof Response) {
           return $limiterResponse;
       } elseif ($limiterResponse instanceof Unlimited) {
           return $next($request);
       }

       return $this->handleRequest(
           $request,
           $next,
           collect(Arr::wrap($limiterResponse))->map(function ($limit) use ($limiterName) {
               return (object) [
                   'key' => self::$shouldHashKeys ? md5($limiterName.$limit->key) : $limiterName.':'.$limit->key,
                   'maxAttempts' => $limit->maxAttempts,
                   'decaySeconds' => $limit->decaySeconds,
                   'responseCallback' => $limit->responseCallback,
               ];
           })->all()
       );
   }

   /**
    * Handle an incoming request.
    *
    * @param  \Illuminate\Http\Request  $request
    * @param  \Closure  $next
    * @param  array  $limits
    * @return \Symfony\Component\HttpFoundation\Response
    *
    * @throws \Illuminate\Http\Exceptions\ThrottleRequestsException
    */
   protected function handleRequest($request, Closure $next, array $limits)
   {
       foreach ($limits as $limit) {
           if ($this->limiter->tooManyAttempts($limit->key, $limit->maxAttempts)) {
               throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback);
           }

           $this->limiter->hit($limit->key, $limit->decaySeconds);
       }

       $response = $next($request);

       foreach ($limits as $limit) {
           $response = $this->addHeaders(
               $response,
               $limit->maxAttempts,
               $this->calculateRemainingAttempts($limit->key, $limit->maxAttempts)
           );
       }

       return $response;
   }

   /**
    * Resolve the number of attempts if the user is authenticated or not.
    *
    * @param  \Illuminate\Http\Request  $request
    * @param  int|string  $maxAttempts
    * @return int
    */
   protected function resolveMaxAttempts($request, $maxAttempts)
   {
       if (str_contains($maxAttempts, '|')) {
           $maxAttempts = explode('|', $maxAttempts, 2)[$request->user() ? 1 : 0];
       }

       if (! is_numeric($maxAttempts) && $request->user()) {
           $maxAttempts = $request->user()->{$maxAttempts};
       }

       return (int) $maxAttempts;
   }

   /**
    * Resolve request signature.
    *
    * @param  \Illuminate\Http\Request  $request
    * @return string
    *
    * @throws \RuntimeException
    */
   protected function resolveRequestSignature($request)
   {
       return sha1(
           $request->method() .
           '|' . $request->server('SERVER_NAME') .
           '|' . $request->path() .
           '|' . $request->ip()
       );

   }

   /**
    * Create a 'too many attempts' exception.
    *
    * @param  \Illuminate\Http\Request  $request
    * @param  string  $key
    * @param  int  $maxAttempts
    * @param  callable|null  $responseCallback
    * @return \Illuminate\Http\Exceptions\ThrottleRequestsException|\Illuminate\Http\Exceptions\HttpResponseException
    */
   protected function buildException($request, $key, $maxAttempts, $responseCallback = null)
   {
       $retryAfter = $this->getTimeUntilNextRetry($key);

       $headers = $this->getHeaders(
           $maxAttempts,
           $this->calculateRemainingAttempts($key, $maxAttempts, $retryAfter),
           $retryAfter
       );

       return new ThrottleException('Too Many Attempts.', 429);
   }

   /**
    * Get the number of seconds until the next retry.
    *
    * @param  string  $key
    * @return int
    */
   protected function getTimeUntilNextRetry($key)
   {
       return $this->limiter->availableIn($key);
   }

   /**
    * 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 \Symfony\Component\HttpFoundation\Response
    */
   protected function addHeaders(Response $response, $maxAttempts, $remainingAttempts, $retryAfter = null)
   {
       $response->headers->add(
           $this->getHeaders($maxAttempts, $remainingAttempts, $retryAfter, $response)
       );

       return $response;
   }

   /**
    * Get the limit headers information.
    *
    * @param  int  $maxAttempts
    * @param  int  $remainingAttempts
    * @param  int|null  $retryAfter
    * @param  \Symfony\Component\HttpFoundation\Response|null  $response
    * @return array
    */
   protected function getHeaders($maxAttempts,
                                 $remainingAttempts,
                                 $retryAfter = null,
                                 Response $response = null)
   {
       if ($response &&
           ! is_null($response->headers->get('X-RateLimit-Remaining')) &&
           (int) $response->headers->get('X-RateLimit-Remaining') <= (int) $remainingAttempts) {
           return [];
       }

       $headers = [
           'X-RateLimit-Limit' => $maxAttempts,
           'X-RateLimit-Remaining' => $remainingAttempts,
       ];

       if (! is_null($retryAfter)) {
           $headers['Retry-After'] = $retryAfter;
           $headers['X-RateLimit-Reset'] = $this->availableAt($retryAfter);
       }

       return $headers;
   }

   /**
    * 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)
   {
       return is_null($retryAfter) ? $this->limiter->retriesLeft($key, $maxAttempts) : 0;
   }

   /**
    * Format the given identifier based on the configured hashing settings.
    *
    * @param  string  $value
    * @return string
    */
   private function formatIdentifier($value)
   {
       return self::$shouldHashKeys ? sha1($value) : $value;
   }

   /**
    * Specify whether rate limiter keys should be hashed.
    *
    * @param  bool  $shouldHashKeys
    * @return void
    */
   public static function shouldHashKeys(bool $shouldHashKeys)
   {
       self::$shouldHashKeys = $shouldHashKeys ?? true;
   }
}

throttle超过限制时抛出的是Illuminate\Http\Exceptions\ThrottleRequestsException,因为Lumen框架缺少这个文件,需要自己定义一下,在app/Exceptions中新建ThrottleException.php,写入以下代码:
具体位置:
在这里插入图片描述
具体代码:

<?php


namespace App\Exceptions;

use Exception;

class ThrottleException extends Exception
{

    protected $isReport = false;

    public function isReport(){
        return $this->isReport;
    }
}

以上代码复制粘贴即可用。

  • 3 同时需要在app/Exceptions/Handler.php捕获该抛出异常,在render方法增加以下判断:
    在这里插入图片描述

代码如下:

<?php

namespace App\Exceptions;

use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Validation\ValidationException;
use Laravel\Lumen\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Throwable;

class Handler extends ExceptionHandler
{
    /**
     * A list of the exception types that should not be reported.
     *
     * @var array
     */
    protected $dontReport = [
        AuthorizationException::class,
        HttpException::class,
        ModelNotFoundException::class,
        ValidationException::class,
    ];

    /**
     * Report or log an exception.
     *
     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
     *
     * @param  \Throwable  $exception
     * @return void
     *
     * @throws \Exception
     */
    public function report(Throwable $exception)
    {
        parent::report($exception);
    }

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Throwable  $exception
     * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     *
     * @throws \Throwable
     */
    public function render($request, Throwable $exception)
    {
    	//此处增加异常捕捉代码
        if ($exception instanceof ThrottleException) {
            return response([
                'code' => $exception->getCode(),
                'msg' => $exception->getMessage()
            ], 429);
        }

        return parent::render($request, $exception);
    }


}

  • 4 增加完以后在app.php中增加中间件:

在这里插入图片描述

// 认证中间件
$app->routeMiddleware([
    'auth' => App\Http\Middleware\Authenticate::class,
    'user_authenticate' => App\Http\Middleware\UserAuthenticateMiddleware::class,
    'xss' => App\Http\Middleware\XSSProtectionMiddleware::class,
    'language' => App\Http\Middleware\LanguageMiddleware::class,
    'currency' => App\Http\Middleware\CurrencyMiddleware::class,
    'throttle' => App\Http\Middleware\ThrottleRequestsMiddleware::class,//此处增加限流中间件
]);
  • 5 在路由相关位置增加使用的中间件即可。

如下:

$router->group([
    'middleware' => ['throttle:60,1'],
],function () use ($router){
	//联系我们
    $router->group(['prefix' => 'contact','as'=>'contact_us'],function () use ($router){
         $router->post('/us', 'ContactUsController@contact');
    });
}

以上代码是关于lumen限流API中间件的,都已经整理好了,复制粘贴即可使用。也可以根据自己需求进行调整。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/347917.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CS BOF文件编写/改写

Beacon Object File(BOF) cs 4.1后添加的新功能&#xff0c; Beacon在接收执行obj前&#xff0c;Cobalt Strike会先对这个obj文件进行一些处理&#xff0c;比如解析obj文件中一些需要的段.text&#xff0c;.data&#xff0c;在处理一些表比如IMAGE_RELOCATION&#xff0c;IMAGE…

QT入门篇---无门槛学习

1.1 什么是 Qt Qt 是⼀个 跨平台的 C 图形⽤⼾界⾯应⽤程序框架 。它为应⽤程序开发者提供了建⽴艺术级图形界⾯所需的所有功能。它是完全⾯向对象的&#xff0c;很容易扩展。Qt 为开发者提供了⼀种基于组件的开发模式&#xff0c;开发者可以通过简单的拖拽和组合来实现复杂的…

RUST笔记 FireDBG| Rust 代码调试器

安装https://firedbg.sea-ql.org/blog/2023-12-12-introducing-firedbg/ 更新VSCODE sudo dpkg -i code_1.85.2-1705561292_amd64.deb 安装FireDBG binaries (base) pddpdd-Dell-G15-5511:~$ curl https://raw.githubusercontent.com/SeaQL/FireDBG.for.Rust/main/install.sh …

[极客大挑战 2019]PHP1

知识点&#xff1a; 1.序列化的属性个数大于实际属性个数可以绕过_wakeup() 详见[CTF]PHP反序列化总结_ctf php反序列化-CSDN博客 2.private属性类名和属性名前都会有多一个NULL&#xff0c;phpstorm运行结果可以显示出来&#xff0c;但是复制出去会变成空格&#xff0c;要手动…

【Single Cell Genomics】Part2 Deep representation learning (form theislab)

文章目录 7 Deep representation learning in single cell genomics7.1 scanpy7.2 DCA7.3 scGen: predicting single-cell perturbation effects7.4 Human cell atlas 来自Manolis Kellis教授&#xff08;MIT计算生物学主任&#xff09;的课 YouTube&#xff1a;Single Cell Ge…

关于达梦认证DCA DCP,TIDB认证PCTA PCTP考试那点事儿

文章最后有彩蛋&#xff0c;一定要看到最后... 一、正确的道路上遇到正确的你 伴随中国数据库领域的快速技术进步&#xff0c;国内数据库生态蓬勃发展&#xff0c;并不断涌现出极具创新力的产品&#xff0c;推动了数据库应用的遍地开花。截至2024年1月&#xff0c;墨天轮数据社…

SWMM模型INP解析

.INP文件解析 [OPTIONS]&#xff1a;SWMM软件运行前需要设置的参数 [RAINGAGES]雨水节点&#xff0c;核心设置雨水时间序列&#xff0c;可为INP内部数据也可为外部txt数据&#xff0c;TIMESERIES对应【TIMESERIES】模块&#xff0c;TS_1为时间序列名称 [TIMESERIES]&#xff0…

红黑树底层实现

什么是红黑树 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red&#xff08;红&#xff09;或Black&#xff08;黑&#xff09;&#xff0c;它是一种比AVL树在使用上更优秀的树&#xff0c;通过对任何一条从根…

微信小程序开发position等于static、relative、absolute、fixed、stricky时元素显示详细介绍

No Position 不设置position时显示,以红色元素做测试: Static 元素根据界面正常流进行定位。top、right、bottom、left 和 z-index 属性不起作用。这是默认值。 红色元素设置position: static,显示如下: Relative 元素根据界面正常流进行定位。以元素当前位置为基准,根…

g2o--ba代码解析

概要 g2o是常用的图优化理论c库&#xff0c;其自带了很多example讲解如何使用该库文件&#xff0c;本文分析其中ba的示例代码。 所谓的图优化&#xff0c;就是把一个常规的优化问题&#xff0c;以图&#xff08;Graph&#xff09;的形式来表述。 在图中&#xff0c;以顶点表…

单片机介绍

本文为博主 日月同辉&#xff0c;与我共生&#xff0c;csdn原创首发。希望看完后能对你有所帮助&#xff0c;不足之处请指正&#xff01;一起交流学习&#xff0c;共同进步&#xff01; > 发布人&#xff1a;日月同辉,与我共生_单片机-CSDN博客 > 欢迎你为独创博主日月同…

Spring Boot 模块工程(通过 Maven Archetype)建立

前言 看到我身边的朋友反馈说&#xff0c;IDEA 新建项目时&#xff0c;如果通过 Spring Initializr 来创建 Spring Boot , 已经无法选择 Java 8 版本&#xff0c;通过上小节的教程&#xff0c;不知道该如何创建 Spring Boot 模块工程。如下图所示&#xff1a; 一.IDEA 搭建 …

记录一下uniapp 集成腾讯im特别卡(已解决)

uniapp的项目运行在微信小程序 , 安卓 , ios手机三端 , 之前这个项目集成过im,不过版本太老了,0.x的版本, 现在需要添加客服功能,所以就升级了 由于是二开 , 也为了方便 , 沿用之前的webview嵌套腾讯IM的方案 , 选用uniapp集成ui ,升级之后所有安卓用户反馈点击进去特别卡,几…

【深度学习】CodeFormer训练过程,如何训练人脸修复模型CodeFormer

文章目录 BasicSR介绍环境数据阶段 I - VQGAN阶段 II - CodeFormer (w0)阶段 III - CodeFormer (w1) 代码地址&#xff1a;https://github.com/sczhou/CodeFormer/releases/tag/v0.1.0 论文的一些简略介绍&#xff1a; https://qq742971636.blog.csdn.net/article/details/134…

Mysql索引相关学习笔记:B+ Tree、索引分类、索引优化、索引失效场景及其他常见面试题

前言 索引是Mysql中常用到的一个功能&#xff0c;可以大大加快查询速度&#xff0c;同时面试中也是经常碰到。本文是学习Mysql索引的归纳总结。 索引采用的数据结构——B 树 本部分主要是参考自小林Coding B树的由来 二分查找可以每次缩减一半&#xff0c;从而提高查找效率…

【mongoDB】数据库的创建和删除

目录 1. 查看所有数据库 2.创建数据库 3.查看当前连接的数据库 4.删除数据库 1. 查看所有数据库 show dbs 2.创建数据库 use 数据库名 例如创建一个名为 aaa 的数据库 3.查看当前连接的数据库 db 4.删除数据库 use 数据库名 db.dropDataBase() 比如删除数据库 aaa

1.25号c++

1.引用 引用就是给变量起别名 格式&#xff1a; 数据类型 &引用名 同类型的变量名 &#xff08;& 引用符号&#xff09; eg: int a 10; int &b a; //b引用a,或者给a变量取个别名叫b int *p; //指针可以先定义 后指向 p &a; //int &a…

【MySQL】如何通过DDL去创建和修改员工信息表

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-fmKISDBsFq74ab2Z {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

docker(第二部分)

来自尚硅谷杨哥 少一点胡思乱想&#xff0c;心中无女人&#xff0c;编码自然神&#xff0c;忘掉心上人&#xff0c;抬手灭红尘。人间清醒&#xff0c;赚钱第一。好好学习&#xff0c;天天向上。听懂六六六。 7.Dokcer容器数据卷 1,&#xff09;坑&#xff1a;容器卷记得加入 …

shared_ptr 与 unique_ptr 的转换 笔记

推荐B站文章&#xff1a; 6.shared_ptr与unique_ptr_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV18B4y187uL?p6&vd_sourcea934d7fc6f47698a29dac90a922ba5a3我的往期文章&#xff1a; 独占指针&#xff1a;unique_ptr 与 函数调用-CSDN博客https://blog.csdn.n…