Viewing file: ThrottleRequests.php (8.39 KB) -rwxrwxr-x Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?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 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 = $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; } }
|