Passed
Pull Request — master (#19)
by Vadim
01:52
created

LimitRequestsMiddleware::addHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
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\LimitingPerUser;
14
use Yiisoft\Yii\RateLimiter\Policy\LimitingPolicy;
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 LimitingPolicy $limitingPolicy;
32
33 5
    public function __construct(
34
        CounterInterface $counter,
35
        ResponseFactoryInterface $responseFactory,
36
        ?LimitingPolicy $limitingPolicy = null
37
    ) {
38 5
        $this->counter = $counter;
39 5
        $this->responseFactory = $responseFactory;
40 5
        $this->limitingPolicy = $limitingPolicy ?: new LimitingPerUser();
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->limit)
68 5
            ->withHeader('X-Rate-Limit-Remaining', (string)$result->remaining)
69 5
            ->withHeader('X-Rate-Limit-Reset', (string)$result->resetTime);
70
    }
71
}
72