Passed
Pull Request — master (#19)
by Vadim
02:11
created

LimitRequestsMiddleware   A

Complexity

Total Complexity 6

Size/Duplication

Total Lines 45
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 20
c 0
b 0
f 0
dl 0
loc 45
ccs 19
cts 19
cp 1
rs 10
wmc 6

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A process() 0 11 2
A addHeaders() 0 6 1
A createErrorResponse() 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 CounterInterface $counter;
28
29
    private ResponseFactoryInterface $responseFactory;
30
31
    private LimitPolicyInterface $limitingPolicy;
32
33 5
    public function __construct(
34
        CounterInterface $counter,
35
        ResponseFactoryInterface $responseFactory,
36
        ?LimitPolicyInterface $limitingPolicy = null
37
    ) {
38 5
        $this->counter = $counter;
39 5
        $this->responseFactory = $responseFactory;
40 5
        $this->limitingPolicy = $limitingPolicy ?: new LimitPerIp();
41 5
    }
42
43 5
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
44
    {
45 5
        $state = $this->counter->hit($this->limitingPolicy->fingerprint($request));
46
47 5
        if ($state->isLimitReached()) {
48 4
            $response = $this->createErrorResponse();
49
        } else {
50 5
            $response = $handler->handle($request);
51
        }
52
53 5
        return $this->addHeaders($response, $state);
54
    }
55
56 4
    private function createErrorResponse(): ResponseInterface
57
    {
58 4
        $response = $this->responseFactory->createResponse(Status::TOO_MANY_REQUESTS);
59 4
        $response->getBody()->write(Status::TEXTS[Status::TOO_MANY_REQUESTS]);
60
61 4
        return $response;
62
    }
63
64 5
    private function addHeaders(ResponseInterface $response, CounterState $result): ResponseInterface
65
    {
66
        return $response
67 5
            ->withHeader('X-Rate-Limit-Limit', (string)$result->getLimit())
68 5
            ->withHeader('X-Rate-Limit-Remaining', (string)$result->getRemaining())
69 5
            ->withHeader('X-Rate-Limit-Reset', (string)$result->getResetTime());
70
    }
71
}
72