mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-14 07:02:34 +01:00
125 lines
3.2 KiB
PHP
125 lines
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Cache\RateLimiter;
|
|
use Illuminate\Redis\Limiters\DurationLimiter;
|
|
use Illuminate\Routing\Middleware\ThrottleRequests;
|
|
|
|
class ThrottleRequestsWithPredis extends ThrottleRequests
|
|
{
|
|
/**
|
|
* The Redis factory implementation.
|
|
*
|
|
* @var \Illuminate\Contracts\Redis\Factory
|
|
*/
|
|
protected $redis;
|
|
|
|
/**
|
|
* The timestamp of the end of the current duration by key.
|
|
*
|
|
* @var array
|
|
*/
|
|
public $decaysAt = [];
|
|
|
|
/**
|
|
* The number of remaining slots by key.
|
|
*
|
|
* @var array
|
|
*/
|
|
public $remaining = [];
|
|
|
|
/**
|
|
* Create a new request throttler.
|
|
*
|
|
* @param \Illuminate\Cache\RateLimiter $limiter
|
|
* @return void
|
|
*/
|
|
public function __construct(RateLimiter $limiter)
|
|
{
|
|
parent::__construct($limiter);
|
|
|
|
$this->redis = \Illuminate\Support\Facades\Redis::connection('sentinel-cache');
|
|
}
|
|
|
|
/**
|
|
* 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->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decayMinutes)) {
|
|
throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback);
|
|
}
|
|
}
|
|
|
|
$response = $next($request);
|
|
|
|
foreach ($limits as $limit) {
|
|
$response = $this->addHeaders(
|
|
$response,
|
|
$limit->maxAttempts,
|
|
$this->calculateRemainingAttempts($limit->key, $limit->maxAttempts)
|
|
);
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Determine if the given key has been "accessed" too many times.
|
|
*
|
|
* @param string $key
|
|
* @param int $maxAttempts
|
|
* @param int $decayMinutes
|
|
* @return mixed
|
|
*/
|
|
protected function tooManyAttempts($key, $maxAttempts, $decayMinutes)
|
|
{
|
|
$limiter = new DurationLimiter(
|
|
$this->redis,
|
|
$key,
|
|
$maxAttempts,
|
|
$decayMinutes * 60
|
|
);
|
|
|
|
return tap(! $limiter->acquire(), function () use ($key, $limiter) {
|
|
[$this->decaysAt[$key], $this->remaining[$key]] = [
|
|
$limiter->decaysAt, $limiter->remaining,
|
|
];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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->remaining[$key] : 0;
|
|
}
|
|
|
|
/**
|
|
* Get the number of seconds until the lock is released.
|
|
*
|
|
* @param string $key
|
|
* @return int
|
|
*/
|
|
protected function getTimeUntilNextRetry($key)
|
|
{
|
|
return $this->decaysAt[$key] - $this->currentTime();
|
|
}
|
|
}
|