Passed
Pull Request — master (#203)
by
unknown
02:14
created

RateLimiter::setCacheKeyCallback()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Web\Middleware;
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 Psr\SimpleCache\CacheInterface;
13
14
final class RateLimiter implements MiddlewareInterface
15
{
16
    private int $limit = 1000;
17
18
    private int $interval = 360;
19
20
    private ?string $cacheKey = null;
21
22
    /**
23
     * @var callable
24
     */
25
    private $cacheKeyCallback;
26
27
    private CacheInterface $cache;
28
29
    private ResponseFactoryInterface $responseFactory;
30
31
    private bool $autoincrement = true;
32
33
    public function __construct(CacheInterface $cache, ResponseFactoryInterface $responseFactory)
34
    {
35
        $this->cache = $cache;
36
        $this->responseFactory = $responseFactory;
37
    }
38
39
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
40
    {
41
        $this->setupCacheParams($request);
42
43
        if (!$this->isAllow()) {
44
            return $this->createErrorResponse();
45
        }
46
47
        if ($this->autoincrement) {
48
            $this->increment();
49
        }
50
51
        return $handler->handle($request);
52
    }
53
54
    public function setLimit(int $limit): self
55
    {
56
        $this->limit = $limit;
57
58
        return $this;
59
    }
60
61
    /**
62
     * @param int $interval in seconds
63
     * @return $this
64
     */
65
    public function setInterval(int $interval): self
66
    {
67
        $this->interval = $interval;
68
69
        return $this;
70
    }
71
72
    public function setCacheKey(string $key): self
73
    {
74
        $this->cacheKey = $key;
75
76
        return $this;
77
    }
78
79
    public function setCacheKeyCallback(callable $callback): self
80
    {
81
        $this->cacheKeyCallback = $callback;
82
83
        return $this;
84
    }
85
86
    public function setAutoIncrement(bool $increment): self
87
    {
88
        $this->autoincrement = $increment;
89
90
        return $this;
91
    }
92
93
    private function createErrorResponse(): ResponseInterface
94
    {
95
        $response = $this->responseFactory->createResponse(429);
96
        $response->getBody()->write('Too Many Requests');
97
98
        return $response;
99
    }
100
101
    private function isAllow(): bool
102
    {
103
        return $this->getCounterValue() < $this->limit;
104
    }
105
106
    private function increment(): void
107
    {
108
        $value = $this->getCounterValue();
109
        $value++;
110
111
        $this->setCounterValue($value);
112
    }
113
114
    private function setupCacheParams(ServerRequestInterface $request): void
115
    {
116
        $this->cacheKey = $this->setupCacheKey($request);
117
    }
118
119
    private function setupCacheKey(ServerRequestInterface $request): string
120
    {
121
        if ($this->cacheKeyCallback !== null) {
122
            return \call_user_func($this->cacheKeyCallback, $request);
123
        }
124
125
        return $this->cacheKey ?? $this->generateCacheKey($request);
126
    }
127
128
    private function generateCacheKey(ServerRequestInterface $request): string
129
    {
130
        return strtolower('rate-limiter-' . $request->getMethod() . '-' . $request->getUri()->getPath());
131
    }
132
133
    private function getCounterValue(): int
134
    {
135
        return $this->cache->get($this->cacheKey, 0);
136
    }
137
138
    private function setCounterValue(int $value): void
139
    {
140
        $this->cache->set($this->cacheKey, $value, $this->interval);
141
    }
142
}
143