laravel 自定义节流中间件

36 篇文章 0 订阅

我以前说过,现在再说一遍,使用框架的最大好处之一是许多常见的用例或问题已经得到解决——限制请求也不例外 。

开箱即用,Laravel cms附带一个ThrottleRequests中间件,该中间件易于配置,可提供 IP 地址在一段时间内可以发出的请求数。

例如,路由文件中的以下代码片段会将用户或 IP 地址可以发出的请求数限制为每分钟 60 次:

Route::middleware('throttle:rate_limit,60')->group(function () {
    Route::get('/products', function () {
        //
    });
    Route::get('/locations', function () {
        //
    });
});

您可以将其设置为3,600默认值,然后根据特定用户的需要向上或向下调整。

但是,如果您想限制一条(或多条)特定路线而不使用这些方法中的任何一种怎么办?

在最近的一个项目中,用户能够添加 IoT 设备并获得一个唯一的 URL,他们可以将其用作 webhook 以将数据发送到我们的系统。在 URL 中使用唯一键,我需要能够限制他们可能发出的请求。

一旦你知道如何实现,这实际上很容易实现——我们只需要了解一点现有ThrottleRequests中间件的工作原理。

如果您打开ThrottleRequests中间件并查看该handle()方法,我们可以看到以下内容:

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)) {
        throw $this->buildException($key, $maxAttempts);
    }
 
    $this->limiter->hit($key, $decayMinutes * 60);
 
    $response = $next($request);
 
    return $this->addHeaders(
        $response, $maxAttempts,
        $this->calculateRemainingAttempts($key, $maxAttempts)
    );
}

该方法的第一行是我们感兴趣的:

$key = $this->resolveRequestSignature($request);

这是ThrottleRequests中间件找出应该用来跟踪您的请求的密钥的地方。

中间件上的方法ThrottleRequests如下所示:

protected function resolveRequestSignature($request)
{
    if ($user = $request->user()) {
        return sha1($user->getAuthIdentifier());
    }
 
    if ($route = $request->route()) {
        return sha1($route->getDomain().'|'.$request->ip());
    }
 
    throw new RuntimeException('Unable to generate the request signature. Route unavailable.');
}

如果您是经过身份验证的用户,那么它将使用id您模型的键名(通常除非您已覆盖它)User。否则,它将使用请求的域和 IP 地址作为密钥。

知道了这一点,我们只需要重写resolveRequestSignature()方法来为请求派生我们自己的密钥:

app/Http/Middleware在called中添加一个新文件CustomThrottleMiddleware(或者你想称呼这个中间件的任何东西)或者使用 scaffold 一些新的中间件php artisan make:middleware CustomThrottleMiddleware 将内容替换为:

<?php
 
namespace App\Http\Middleware;
 
use Illuminate\Routing\Middleware\ThrottleRequests;
 
class CustomThrottleMiddleware extends ThrottleRequests
{
    protected function resolveRequestSignature($request)
    {
        //
    }
}

我们所做的是创建我们自己的新中间件,它扩展了现有的ThrottleRequests中间件,现在设置为覆盖该resolveRequestSignature()方法,以便我们可以设置我们自己的密钥。

现在你只需要将你的代码添加到resolveRequestSignature()方法中以实际返回一个键——我将在下面包括一些例子:

protected function resolveRequestSignature($request)
{
    // Throttle by a particular header
    return $request->header('API-Key');
}
protected function resolveRequestSignature($request)
{
    // Throttle by a request parameter
    return $request->input('account_id');
}
protected function resolveRequestSignature($request)
{
    // Throttle by session ID
    return $request->session();
}
protected function resolveRequestSignature($request)
{
    // Throttle by IP to a particular Model in the route - where `product` is our model and this route is set to use `product` with route model binding
    return $request->route('product') . '|' . $request->ip();
}

 在我的用例中,我选择了通过多个参数进行节流。我们为用户提供的唯一 URL 对他们来说是唯一的,但对于所有相同类型的物联网设备都是相同的。例如,我们支持的物联网设备类型A为 、BC;如果您有两种类型,A那么它们共享相同的唯一 URL。我们对此的推理对这篇文章并不重要,所以我将其省略。

每个设备被允许每 5 分钟发出一个请求,但是由于两个类型A的设备在技术上是两个独立的设备,我们需要包括一种将两者分开的额外方法。

为实现这一目标,我们只是弄清楚我们使用的是什么类型的物联网设备,然后从那里适当地解析令牌:

protected function resolveRequestSignature($request)
{
    $token = $request->route()->parameter('deviceByToken');
 
    $device = IotDevice::findByToken($token)->firstOrFail();
 
    if ($device->type === 'A')
    {
        return $token . '-' . $request->input('unique_parameter_for_type_a_per_device');
    } else if ($device->type === 'B')
    {
        return $token . '-' . $request->input('unique_parameter_for_type_b_per_device');
    } if ($device->type === 'C')
    {
        return $token . '-' . $request->input('unique_parameter_for_type_c_per_device');
    }
 
    return $token;
}

最后但同样重要的是,您只需app/Http/Kernel.php像这样更新文件来注册中间件以供使用:

 

protected $routeMiddleware = [
    //...
    'customthrottle' => \App\Http\Middleware\CustomThrottleMiddleware::class,
];

然后,这允许您像这样使用新的中间件(替换 1 和 5 以满足您的需要):

Route::middleware('customthrottle:1,5')->group(function () {
    Route::get('/products', function () {
        //
    });
    Route::get('/locations', function () {
        //
    });
});

使用上述方法,我们能够使用每个请求中存在的唯一路由参数以及每个设备在请求的有效负载中包含的唯一标识符来限制请求 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

rorg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值