Blame | Letzte Änderung | Log anzeigen | RSS feed
<?phpnamespace 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;}}