Skip to content

Commit aba60ae

Browse files
authored
Respect rate limits (#63)
The NitrAPI has a rate limit in place. This metadata will be stored in the header of each response. To access that, getters can be used. If a user reaches the rate limit, the NitrAPI refuses all requests. To handle that, a NitrapiRateLimitException will be thrown, so the client can handle that case properly.
1 parent a3cfacc commit aba60ae

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Nitrapi\Common\Exceptions;
4+
5+
/**
6+
* NitrapiRateLimitException
7+
*
8+
* The NitrAPI has some rate limits in place, which limits the number of requests
9+
* the user can perform in one hour. This limit will be reset every hour, so if
10+
* the user ran into a rate limit, it will be released by no later than one hour.
11+
* The rate limit besides the reset time and the remaining tasks will be sent as
12+
* a header with every response.
13+
*
14+
* If the rate limit is reached, the NitrAPI refuses all request. If that happen,
15+
* we throw this exception, so the client can handle this case properly.
16+
*
17+
* @package Nitrapi\Common\Exceptions
18+
*/
19+
class NitrapiRateLimitException extends NitrapiException {
20+
private $rateLimit;
21+
private $resetTime;
22+
23+
public function __construct($rateLimit, $resetTime) {
24+
$this->rateLimit = $rateLimit;
25+
$this->resetTime = $resetTime;
26+
27+
parent::__construct("The rate limit ($rateLimit requests in one hour) is exceeded. You need to wait until $resetTime make another request.");
28+
}
29+
30+
public function getRateLimit() {
31+
return $this->rateLimit;
32+
}
33+
34+
public function getResetTime() {
35+
return $this->resetTime;
36+
}
37+
}

lib/Nitrapi/Common/Http/Client.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
namespace Nitrapi\Common\Http;
44

5+
use DateTime;
56
use GuzzleHttp\Client as GuzzleClient;
67
use GuzzleHttp\Exception\RequestException;
78
use GuzzleHttp\Psr7\Response;
89
use Nitrapi\Common\Exceptions\NitrapiConcurrencyException;
910
use Nitrapi\Common\Exceptions\NitrapiException;
1011
use Nitrapi\Common\Exceptions\NitrapiHttpErrorException;
1112
use Nitrapi\Common\Exceptions\NitrapiMaintenanceException;
13+
use Nitrapi\Common\Exceptions\NitrapiRateLimitException;
1214

1315
class Client extends GuzzleClient
1416
{
@@ -18,6 +20,14 @@ class Client extends GuzzleClient
1820

1921
protected $accessToken = null;
2022

23+
// Rate Limit metadata
24+
/** @var integer */
25+
protected $rateLimit;
26+
/** @var integer */
27+
protected $remainingRequests;
28+
/** @var DateTime */
29+
protected $rateLimitResetTime;
30+
2131
protected $clientCertificate;
2232
protected $clientCertificateKey;
2333

@@ -82,7 +92,70 @@ public function fillOptions(&$options) {
8292
}
8393
}
8494

95+
/**
96+
* Rate limit
97+
*
98+
* @return int The number of requests which are allowed in one hour.
99+
*/
100+
public function getRateLimit() {
101+
return $this->rateLimit;
102+
}
103+
104+
/**
105+
* Check for rate limit
106+
*
107+
* @return bool if there is a rate limit in place
108+
*/
109+
public function hasRateLimit() {
110+
return $this->rateLimit !== false;
111+
}
112+
113+
/**
114+
* Remaining requests
115+
*
116+
* @return int The number of requests remaining until the rate limit is exceeded.
117+
*/
118+
public function getRemainingRequests() {
119+
return $this->remainingRequests;
120+
}
121+
122+
/**
123+
* Rate limit reset time
124+
*
125+
* @return DateTime The time the rate limit will be reset
126+
*/
127+
public function getRateLimitResetTime() {
128+
return $this->rateLimitResetTime;
129+
}
130+
131+
/**
132+
* Parse the NitrAPI response
133+
*
134+
* @param Response $response
135+
* @return bool|mixed true if response is fine but without message, data or message otherwise.
136+
* @throws NitrapiHttpErrorException when the API responds with an error message.
137+
* @throws NitrapiRateLimitException when the user ran into the rate limit.
138+
*/
85139
public function parseResponse(Response $response) {
140+
// Rate limit metadata
141+
if ($response->hasHeader('X-RateLimit-Limit')) {
142+
$this->rateLimit = $response->getHeader('X-RateLimit-Limit')[0];
143+
$this->remainingRequests = $response->getHeader('X-RateLimit-Remaining')[0];
144+
$resetDateTime = new DateTime();
145+
$resetDateTime->setTimestamp($response->getHeader('X-RateLimit-Reset')[0]);
146+
$this->rateLimitResetTime = $resetDateTime;
147+
148+
// We ran into the rate limit, so we throw an exception with all needed information.
149+
// This gives the client the option to handle that error. To access the rate limit
150+
// metadata, the getRateLimit(), getRemainingRequests() and getRateLimitResetTime()
151+
// method can be used at any time.
152+
if ($response->getStatusCode() === 429) {
153+
throw new NitrapiRateLimitException($this->getRateLimit(), $this->getRateLimitResetTime());
154+
}
155+
} else {
156+
$this->rateLimit = false;
157+
}
158+
86159
$contentType = $response->getHeader('Content-Type')[0];
87160

88161
// Return plain text

0 commit comments

Comments
 (0)