Skip to content

Commit a1c5f63

Browse files
authored
Merge pull request #4 from dannidickson/feature/handle_rate_limiting
Feature/handle rate limiting
2 parents 22fefdb + 293c566 commit a1c5f63

File tree

1 file changed

+57
-22
lines changed

1 file changed

+57
-22
lines changed

code/Workable.php

+57-22
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,10 @@
33
namespace SilverStripe\Workable;
44

55
use GuzzleHttp\ClientInterface;
6-
use GuzzleHttp\Exception\ClientException;
7-
use Monolog\Logger;
8-
use RuntimeException;
6+
use GuzzleHttp\Exception\RequestException;
97
use Psr\Log\LoggerInterface;
108
use SilverStripe\ORM\ArrayList;
119
use SilverStripe\Core\Flushable;
12-
use SilverStripe\View\ArrayData;
1310
use SilverStripe\Core\Extensible;
1411
use Psr\SimpleCache\CacheInterface;
1512
use SilverStripe\Core\Injector\Injector;
@@ -65,7 +62,7 @@ public function getJobs(array $params = []): ArrayList
6562
}
6663

6764
$list = ArrayList::create();
68-
$response = $this->callHttpClient('jobs', $params);
65+
$response = $this->callWorkableApi('jobs', $params);
6966

7067
if (!$response) {
7168
return $list;
@@ -97,7 +94,7 @@ public function getJob(string $shortcode, array $params = []): ?WorkableResult
9794
}
9895

9996
$job = null;
100-
$response = $this->callHttpClient('jobs/' . $shortcode, $params);
97+
$response = $this->callWorkableApi('jobs/' . $shortcode, $params);
10198

10299
if ($response && isset($response['id'])) {
103100
$job = WorkableResult::create($response);
@@ -109,8 +106,7 @@ public function getJob(string $shortcode, array $params = []): ?WorkableResult
109106

110107
/**
111108
* Gets all the jobs from the workable API, populating each job with its full data
112-
* Note: This calls the API multiple times so should be used with caution, see
113-
* rate limiting docs https://workable.readme.io/docs/rate-limits
109+
* Note: This calls the API multiple times so should be used with caution
114110
* @param array $params Array of params, e.g. ['state' => 'published'].
115111
* see https://workable.readme.io/docs/jobs for full list of query params
116112
* @return ArrayList
@@ -124,7 +120,7 @@ public function getFullJobs($params = [])
124120
}
125121

126122
$list = ArrayList::create();
127-
$response = $this->callHttpClient('jobs', $params);
123+
$response = $this->callWorkableApi('jobs', $params);
128124

129125
if (!$response) {
130126
return $list;
@@ -142,30 +138,67 @@ public function getFullJobs($params = [])
142138
}
143139

144140
/**
145-
* Wrapper method to configure the RestfulService, make the call, and handle errors
141+
* Sends request to Workable API.
142+
* Should it exceed the rate limit, this is caught and put to sleep until the next interval.
143+
* The interval duration is provided by Workable via a header.
144+
* When its awaken, it will call itself again, this repeats until its complete.
145+
* This returns a json body from the response.
146+
*
147+
* Note: See rate limit docs from Workable https://workable.readme.io/docs/rate-limits
146148
* @param string $url
147149
* @param array $params
148150
* @param string $method
149151
*
150-
* @throws RuntimeException if client is not configured correctly
151-
* @throws ClientException if request fails
152-
*
152+
* @throws RequestException if client is not configured correctly, handles 429 error
153+
153154
* @return array JSON as array
154155
*/
155-
public function callHttpClient(string $url, array $params = [], string $method = 'GET'): array
156+
public function callWorkableApi(string $url, array $params = [], string $method = 'GET'): array
156157
{
157158
try {
158159
$response = $this->httpClient->request($method, $url, ['query' => $params]);
159-
} catch (\RuntimeException $e) {
160-
Injector::inst()->get(LoggerInterface::class)->warning(
161-
'Failed to retrieve valid response from workable',
162-
['exception' => $e]
163-
);
164-
165-
throw $e;
160+
return json_decode($response->getBody(), true);
161+
}
162+
catch(RequestException $e){
163+
if($e->hasResponse()){
164+
$errorResponse = $e->getResponse();
165+
$statusCode = $errorResponse->getStatusCode();
166+
167+
if($statusCode === 429) {
168+
Injector::inst()->get(LoggerInterface::class)->info(
169+
'Rate limit exceeded - sleeping until next interval'
170+
);
171+
172+
$this->sleepUntil($errorResponse->getHeader('X-Rate-Limit-Reset'));
173+
174+
return $this->callWorkableApi($url, $params, $method);
175+
}
176+
else {
177+
Injector::inst()->get(LoggerInterface::class)->warning(
178+
'Failed to retrieve valid response from workable',
179+
['exception' => $e]
180+
);
181+
182+
throw $e;
183+
}
184+
}
166185
}
186+
}
167187

168-
return json_decode($response->getBody(), true);
188+
/**
189+
* Sleeps until the next interval.
190+
* Should the interval header be empty, the script sleeps for 10 seconds - Workable's default interval.
191+
* @param array $resetIntervalHeader
192+
*/
193+
private function sleepUntil($resetIntervalHeader){
194+
$defaultSleepInterval = 10;
195+
196+
if(!empty($resetIntervalHeader)){
197+
time_sleep_until($resetIntervalHeader[0]);
198+
}
199+
else {
200+
sleep($defaultSleepInterval);
201+
}
169202
}
170203

171204
/**
@@ -177,6 +210,7 @@ public static function flush()
177210
}
178211

179212
/**
213+
* Gets any cached data. If there is no cached data, a blank cache is created.
180214
* @return CacheInterface
181215
*/
182216
public function getCache(): CacheInterface
@@ -189,6 +223,7 @@ public function getCache(): CacheInterface
189223
}
190224

191225
/**
226+
* Sets the cache.
192227
* @param CacheInterface $cache
193228
* @return self
194229
*/

0 commit comments

Comments
 (0)