Passed
Pull Request — master (#127)
by
unknown
04:29
created

RequestLimiter   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 193
Duplicated Lines 0 %

Test Coverage

Coverage 96.67%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 20
eloc 54
c 2
b 0
f 0
dl 0
loc 193
ccs 58
cts 60
cp 0.9667
rs 10

12 Methods

Rating   Name   Duplication   Size   Complexity  
A matches() 0 3 2
A _buildHostUrl() 0 13 3
A _getHostFromRequestAndOptions() 0 5 1
A getRemainingSeconds() 0 3 1
A _save() 0 8 1
A createFromRule() 0 3 1
A _increment() 0 6 2
A create() 0 3 1
A _restoreState() 0 12 2
A __construct() 0 9 1
A canRequest() 0 16 3
A getCurrentRequestCount() 0 9 2
1
<?php
2
3
namespace hamburgscleanest\GuzzleAdvancedThrottle;
4
5
use GuzzleHttp\Psr7\Uri;
6
use hamburgscleanest\GuzzleAdvancedThrottle\Cache\Adapters\ArrayAdapter;
7
use hamburgscleanest\GuzzleAdvancedThrottle\Cache\Interfaces\StorageInterface;
8
use Psr\Http\Message\RequestInterface;
9
10
/**
11
 * Class RequestLimiter
12
 * @package hamburgscleanest\GuzzleAdvancedThrottle
13
 */
14
class RequestLimiter
15
{
16
17
    /** @var int */
18
    private const DEFAULT_MAX_REQUESTS = 120;
19
    /** @var int */
20
    private const DEFAULT_REQUEST_INTERVAL = 60;
21
    /** @var string */
22
    private $_host;
23
    /** @var TimeKeeper */
24
    private $_timekeeper;
25
    /** @var int */
26
    private $_requestCount = 0;
27
    /** @var int */
28
    private $_maxRequestCount;
29
    /** @var StorageInterface */
30
    private $_storage;
31
    /** @var string */
32
    private $_storageKey;
33
34
    /**
35
     * RequestLimiter constructor.
36
     * @param string $host
37
     * @param int $maxRequests
38
     * @param int $requestIntervalSeconds
39
     * @param StorageInterface|null $storage
40
     * @throws \Exception
41
     */
42 31
    public function __construct(string $host, ?int $maxRequests = self::DEFAULT_MAX_REQUESTS, ?int $requestIntervalSeconds = self::DEFAULT_REQUEST_INTERVAL, StorageInterface $storage = null)
43
    {
44 31
        $this->_storage = $storage ?? new ArrayAdapter();
45 31
        $this->_host = $host;
46 31
        $this->_maxRequestCount = $maxRequests ?? self::DEFAULT_MAX_REQUESTS;
47 31
        $requestInterval = $requestIntervalSeconds ?? self::DEFAULT_REQUEST_INTERVAL;
48
49 31
        $this->_storageKey = $this->_maxRequestCount  . '_' . $requestInterval;
50 31
        $this->_restoreState($requestInterval);
51 31
    }
52
53
    /**
54
     * @param int $requestIntervalSeconds
55
     * @throws \Exception
56
     */
57 31
    private function _restoreState(int $requestIntervalSeconds) : void
58
    {
59 31
        $this->_timekeeper = new TimeKeeper($requestIntervalSeconds);
60
61 31
        $requestInfo = $this->_storage->get($this->_host, $this->_storageKey);
62 31
        if ($requestInfo === null)
63
        {
64 31
            return;
65
        }
66
67 13
        $this->_requestCount = $requestInfo->requestCount;
68 13
        $this->_timekeeper->setExpiration($requestInfo->expiresAt);
69 13
    }
70
71
    /**
72
     * @param string $host
73
     * @param array $rule
74
     * @param StorageInterface|null $storage
75
     * @return RequestLimiter
76
     * @throws \Exception
77
     */
78 20
    public static function createFromRule(string $host, array $rule, StorageInterface $storage = null) : self
79
    {
80 20
        return new static($host, $rule['max_requests'] ?? null, $rule['request_interval'] ?? null, $storage);
81
    }
82
83
    /**
84
     * @param string $host
85
     * @param int $maxRequests
86
     * @param int $requestIntervalSeconds
87
     * @param StorageInterface|null $storage
88
     * @return RequestLimiter
89
     * @throws \Exception
90
     */
91 7
    public static function create(string $host, ?int $maxRequests = self::DEFAULT_MAX_REQUESTS, ?int $requestIntervalSeconds = self::DEFAULT_REQUEST_INTERVAL, StorageInterface $storage = null) : self
92
    {
93 7
        return new static($host, $maxRequests, $requestIntervalSeconds, $storage);
94
    }
95
96
    /**
97
     * @param RequestInterface $request
98
     * @param array $options
99
     * @return bool
100
     * @throws \Exception
101
     */
102 26
    public function canRequest(RequestInterface $request, array $options = []) : bool
103
    {
104 26
        if (!$this->matches($this->_getHostFromRequestAndOptions($request, $options)))
105
        {
106 8
            return true;
107
        }
108
109 18
        if ($this->getCurrentRequestCount() >= $this->_maxRequestCount)
110
        {
111 17
            return false;
112
        }
113
114 14
        $this->_increment();
115 14
        $this->_save();
116
117 14
        return true;
118
    }
119
120
    /**
121
     * @param string $host
122
     * @return bool
123
     */
124 27
    public function matches(string $host) : bool
125
    {
126 27
        return $this->_host === $host || Wildcard::matches($this->_host, $host);
127
    }
128
129
    /**
130
     * @param RequestInterface $request
131
     * @param array $options
132
     * @return string
133
     */
134 26
    private function _getHostFromRequestAndOptions(RequestInterface $request, array $options = []) : string
135
    {
136 26
        $uri = $options['base_uri'] ?? $request->getUri();
137
138 26
        return $this->_buildHostUrl($uri);
139
    }
140
141
    /**
142
     * @param Uri $uri
143
     * @return string
144
     */
145 26
    private function _buildHostUrl(Uri $uri) : string
146
    {
147 26
        $host = $uri->getHost();
148 26
        $scheme = $uri->getScheme();
149 26
        if (!empty($host) && !empty($scheme))
150
        {
151 8
            $host = $scheme . '://' . $host.$uri->getPath();
152
        } else
153
        {
154 18
            $host = $uri->getPath();
155
        }
156
157 26
        return $host;
158
    }
159
160
    /**
161
     * Increment the request counter.
162
     * @throws \Exception
163
     */
164 14
    private function _increment() : void
165
    {
166 14
        $this->_requestCount++;
167 14
        if ($this->_requestCount === 1)
168
        {
169 14
            $this->_timekeeper->start();
170
        }
171 14
    }
172
173
    /**
174
     * Save timer in storage
175
     */
176 14
    private function _save() : void
177
    {
178 14
        $this->_storage->save(
179 14
            $this->_host,
180 14
            $this->_storageKey,
181 14
            $this->_requestCount,
182 14
            $this->_timekeeper->getExpiration(),
183 14
            $this->getRemainingSeconds()
184
        );
185 14
    }
186
187
    /**
188
     * @return int
189
     */
190 20
    public function getRemainingSeconds() : int
191
    {
192 20
        return $this->_timekeeper->getRemainingSeconds();
193
    }
194
195
    /**
196
     * @return int
197
     */
198 21
    public function getCurrentRequestCount() : int
199
    {
200 21
        if ($this->_timekeeper->isExpired())
201
        {
202
            $this->_timekeeper->reset();
203
            $this->_requestCount = 0;
204
        }
205
206 21
        return $this->_requestCount;
207
    }
208
}