Passed
Pull Request — master (#19)
by Vadim
02:20
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
14
/**
15
 * RateLimiter helps to prevent abuse by limiting the number of requests that could be me made consequentially.
16
 *
17
 * 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
18
 * minutes. If too many requests are received from a user within the stated period of the time, a response with status
19
 * code 429 (meaning "Too Many Requests") should be returned.
20
 *
21
 * @psalm-type CounterIdCallback = callable(ServerRequestInterface):string
22
 */
23
final class LimitRequestsMiddleware implements MiddlewareInterface
24
{
25
    private CounterInterface $counter;
26
27
    private ResponseFactoryInterface $responseFactory;
28
29
    private LimitingPolicy $limitingPolicy;
30
31 4
    public function __construct(
32
        CounterInterface $counter,
33
        ResponseFactoryInterface $responseFactory,
34
        ?LimitingPolicy $limitingPolicy = null
35
    ) {
36 4
        $this->counter = $counter;
37 4
        $this->responseFactory = $responseFactory;
38 4
        $this->limitingPolicy = $limitingPolicy ?: new LimitingPerUser();
39 4
    }
40
41 4
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
42
    {
43 4
        $state = $this->counter->hit($this->limitingPolicy->fingerprint($request));
44
45 4
        if ($state->isLimitReached) {
46 3
            $response = $this->createErrorResponse();
47
        } else {
48 4
            $response = $handler->handle($request);
49
        }
50
51 4
        return $this->addHeaders($response, $state);
52
    }
53
54 3
    private function createErrorResponse(): ResponseInterface
55
    {
56 3
        $response = $this->responseFactory->createResponse(Status::TOO_MANY_REQUESTS);
57 3
        $response->getBody()->write(Status::TEXTS[Status::TOO_MANY_REQUESTS]);
58
59 3
        return $response;
60
    }
61
62 4
    private function addHeaders(ResponseInterface $response, CounterState $result): ResponseInterface
63
    {
64
        return $response
65 4
            ->withHeader('X-Rate-Limit-Limit', (string)$result->limit)
66 4
            ->withHeader('X-Rate-Limit-Remaining', (string)$result->remaining)
67 4
            ->withHeader('X-Rate-Limit-Reset', (string)$result->resetTime);
68
    }
69
}
70