getIPAddress() . '|' . date('YmdH')); $sessionKey = service('analytics')->sessionHash(); $sessBucket = 'rl_ses_' . substr($sessionKey, 0, 16) . '|' . date('YmdH'); $cache = cache(); $ipCount = (int) ($cache->get($ipBucket) ?? 0); $seCount = (int) ($cache->get($sessBucket) ?? 0); // API tokens carry their own generous quota and bypass the IP window $isApi = str_starts_with(uri_string(), 'api/v1') && $request->getHeaderLine('Authorization') !== ''; if (! $isApi && $ipCount >= $site->maxJobsPerHourIp * 3) { return $this->tooMany(); } if ($seCount >= $site->maxJobsPerHourSession * 3) { return $this->tooMany(); } $cache->save($ipBucket, $ipCount + 1, 3700); $cache->save($sessBucket, $seCount + 1, 3700); return null; } private function tooMany(): ResponseInterface { return response() ->setStatusCode(429) ->setBody(view('errors/rate_limited')); } public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { return null; } }