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

LimitRequestsMiddleware::process()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 11
ccs 6
cts 6
cp 1
crap 2
rs 10
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