Laravel:从自定义中间件油门组返回 bool
Posted
技术标签:
【中文标题】Laravel:从自定义中间件油门组返回 bool【英文标题】:Laravel: return bool from custom middleware throttle group 【发布时间】:2022-01-09 00:14:29 【问题描述】:我想在我的 Laravel 应用的中间件路由组中实现我自己的自定义逻辑。
我现在可以用 Laravel 开箱即用:
Route::middleware('throttle:10,1')->group(function ()
Route::get('/test/throttle', function()
return response('OK', 200)->header('Content-Type', 'text/html');
);
);
因此,正如预期的那样,它将默认的 429 代码视图返回为 HttpResponseException。
但我想覆盖油门中间件并只返回一个 (bool) tooManyAttempts
值作为函数参数:
Route::middleware('customthrottle:10,1')->group(function ($tooManyAttempts)
Route::get('/test/throttle', function()
if ($tooManyAttempts)
return response("My custom 'Too many attempts' page only for this route", 200)->header('Content-Type', 'text/html');
else
return response('You are good to go yet, he-he', 200)->header('Content-Type', 'text/html');
);
);
我阅读了this article,但我不知道如何以可以将一些值作为函数参数传递的方式覆盖它。它可能是 (bool) $tooManyAttempts
value 或至少 (int) $maxAttempts
value。
究竟是什么方法,我应该以什么方式重写来做到这一点?
(vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php
)
<?php
namespace Illuminate\Routing\Middleware;
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 Illuminate\Support\Str;
use RuntimeException;
use Symfony\Component\HttpFoundation\Response;
class ThrottleRequests
use InteractsWithTime;
/**
* The rate limiter instance.
*
* @var \Illuminate\Cache\RateLimiter
*/
protected $limiter;
/**
* Create a new request throttler.
*
* @param \Illuminate\Cache\RateLimiter $limiter
* @return void
*/
public function __construct(RateLimiter $limiter)
$this->limiter = $limiter;
/**
* 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),
'decayMinutes' => $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 = call_user_func($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' => md5($limiterName.$limit->key),
'maxAttempts' => $limit->maxAttempts,
'decayMinutes' => $limit->decayMinutes,
'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->decayMinutes * 60);
$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)
if ($user = $request->user())
return sha1($user->getAuthIdentifier());
elseif ($route = $request->route())
return sha1($route->getDomain().'|'.$request->ip());
throw new RuntimeException('Unable to generate the request signature. Route unavailable.');
/**
* 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
*/
protected function buildException($request, $key, $maxAttempts, $responseCallback = null)
$retryAfter = $this->getTimeUntilNextRetry($key);
$headers = $this->getHeaders(
$maxAttempts,
$this->calculateRemainingAttempts($key, $maxAttempts, $retryAfter),
$retryAfter
);
return is_callable($responseCallback)
? new HttpResponseException($responseCallback($request, $headers))
: new ThrottleRequestsException('Too Many Attempts.', null, $headers);
/**
* 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;
【问题讨论】:
这是一个 xy 问题,您已经得出结论,您的中间件需要返回一个布尔值。您的问题不是更多的是您需要在限制请求时自定义响应吗? 感谢您的回复。我认为这是一个非常简单的问题,我只是没有经验来解决这些任务。我也找不到任何与此问题类似的可靠示例。此外,覆盖中间件组以返回 bool 值将是此任务最方便的方法。但我会很感激任何建议,即使是定制油门响应的例子,它不会破坏所需的逻辑。 但是响应是问题还是您想要实现的目标,而不谈论潜在的解决方案? 【参考方案1】:按照 cmets 中的建议,您可以通过一种方法来实现您在控制器逻辑中所做的事情,即覆盖 Exceptions/Handler.php
类中的异常。
public function render($request, Exception $exception)
if ($exception instanceof ThrottleRequestsException)
return response('Too many attempts', 200)->header('Content-Type', 'text/html');
【讨论】:
如果这会影响所有中间件组的所有限制异常,我是对的吗?如果是这样,不幸的是,这并不能解决问题。以上是关于Laravel:从自定义中间件油门组返回 bool的主要内容,如果未能解决你的问题,请参考以下文章
.Net Core:从自定义异常中间件返回 IActionResult
laravel 在使用 php artisan 时从自定义存根创建模型