LimitRequestsMiddleware   A
last analyzed

Complexity

Total Complexity 6

Size/Duplication

Total Lines 39
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 6
eloc 16
c 0
b 0
f 0
dl 0
loc 39
ccs 17
cts 17
cp 1
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
A process() 0 11 2
A createErrorResponse() 0 6 1
A addHeaders() 0 6 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\RateLimiter;
6
7
use Psr\Http\Message\ResponseFactoryInterface;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\ServerRequestInterface;
10
use Psr\Http\Server\MiddlewareInterface;
11
use Psr\Http\Server\RequestHandlerInterface;
12
use Yiisoft\Http\Status;
13
use Yiisoft\Yii\RateLimiter\Policy\LimitPerIp;
14
use Yiisoft\Yii\RateLimiter\Policy\LimitPolicyInterface;
15
16
/**
17
 * RateLimiter helps to prevent abuse by limiting the number of requests that could be me made consequentially.
18
 *
19
 * For example, you may want to limit the API usage of each user to be at most 100 API calls within a period of 10
20
 * minutes. If too many requests are received from a user within the stated period of the time, a response with status
21
 * code 429 (meaning "Too Many Requests") should be returned.
22
 *
23
 * @psalm-type CounterIdCallback = callable(ServerRequestInterface):string
24
 */
25
final class LimitRequestsMiddleware implements MiddlewareInterface
26
{
27
    private LimitPolicyInterface $limitingPolicy;
28
29 5
    public function __construct(
30
        private CounterInterface $counter,
31
        private ResponseFactoryInterface $responseFactory,
32
        LimitPolicyInterface|null $limitingPolicy = null
33
    ) {
34 5
        $this->limitingPolicy = $limitingPolicy ?: new LimitPerIp();
35
    }
36
37 5
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
38
    {
39 5
        $state = $this->counter->hit($this->limitingPolicy->fingerprint($request));
40
41 5
        if ($state->isLimitReached()) {
42 4
            $response = $this->createErrorResponse();
43
        } else {
44 5
            $response = $handler->handle($request);
45
        }
46
47 5
        return $this->addHeaders($response, $state);
48
    }
49
50 4
    private function createErrorResponse(): ResponseInterface
51
    {
52 4
        $response = $this->responseFactory->createResponse(Status::TOO_MANY_REQUESTS);
53 4
        $response->getBody()->write(Status::TEXTS[Status::TOO_MANY_REQUESTS]);
54
55 4
        return $response;
56
    }
57
58 5
    private function addHeaders(ResponseInterface $response, CounterState $result): ResponseInterface
59
    {
60 5
        return $response
61 5
            ->withHeader('X-Rate-Limit-Limit', (string)$result->getLimit())
62 5
            ->withHeader('X-Rate-Limit-Remaining', (string)$result->getRemaining())
63 5
            ->withHeader('X-Rate-Limit-Reset', (string)$result->getResetTime());
64
    }
65
}
66