ThrottleRequestsWithRedis.php
TLDR
This file, ThrottleRequestsWithRedis.php
, is part of the Illuminate\Routing\Middleware namespace in the Laravel framework. It provides a request throttling middleware that uses Redis for storing and managing request limits.
Methods
handleRequest
This method handles an incoming request by checking if the request has exceeded the specified limits. If the request has exceeded the limit, it throws a ThrottleRequestsException
. Otherwise, it adds headers to the response with the maximum number of attempts and the remaining attempts.
tooManyAttempts
This method determines if the given key has been accessed too many times by using the DurationLimiter
class from the Illuminate\Redis\Limiters
namespace. It returns true
if the limiter is unable to acquire the lock, indicating that the request has exceeded the limit.
calculateRemainingAttempts
This method calculates the number of remaining attempts based on the given key
and maxAttempts
. If a retryAfter
value is provided, it returns 0
.
getTimeUntilNextRetry
This method calculates the number of seconds until the lock is released for the given key
. It subtracts the current time from the decaysAt
timestamp.
getRedisConnection
This method returns the Redis connection that should be used for throttling.
Classes
ThrottleRequestsWithRedis
This class extends the ThrottleRequests
class and implements the request throttling functionality using Redis. It maintains properties to store the Redis
factory implementation, the decaysAt
timestamp for each key, and the number of remaining slots for each key. It overrides the handleRequest
method to handle the request throttling logic.
<?php
namespace Illuminate\Routing\Middleware;
use Closure;
use Illuminate\Cache\RateLimiter;
use Illuminate\Contracts\Redis\Factory as Redis;
use Illuminate\Redis\Limiters\DurationLimiter;
class ThrottleRequestsWithRedis 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
* @param \Illuminate\Contracts\Redis\Factory $redis
* @return void
*/
public function __construct(RateLimiter $limiter, Redis $redis)
{
parent::__construct($limiter);
$this->redis = $redis;
}
/**
* 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->decaySeconds)) {
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 $decaySeconds
* @return mixed
*/
protected function tooManyAttempts($key, $maxAttempts, $decaySeconds)
{
$limiter = new DurationLimiter(
$this->getRedisConnection(), $key, $maxAttempts, $decaySeconds
);
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();
}
/**
* Get the Redis connection that should be used for throttling.
*
* @return \Illuminate\Redis\Connections\Connection
*/
protected function getRedisConnection()
{
return $this->redis->connection();
}
}