Skip to content

Commit

Permalink
Merge pull request #4 from dannidickson/feature/handle_rate_limiting
Browse files Browse the repository at this point in the history
Feature/handle rate limiting
  • Loading branch information
scott1702 authored Jul 7, 2021
2 parents 22fefdb + 293c566 commit a1c5f63
Showing 1 changed file with 57 additions and 22 deletions.
79 changes: 57 additions & 22 deletions code/Workable.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
namespace SilverStripe\Workable;

use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\ClientException;
use Monolog\Logger;
use RuntimeException;
use GuzzleHttp\Exception\RequestException;
use Psr\Log\LoggerInterface;
use SilverStripe\ORM\ArrayList;
use SilverStripe\Core\Flushable;
use SilverStripe\View\ArrayData;
use SilverStripe\Core\Extensible;
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Core\Injector\Injector;
Expand Down Expand Up @@ -65,7 +62,7 @@ public function getJobs(array $params = []): ArrayList
}

$list = ArrayList::create();
$response = $this->callHttpClient('jobs', $params);
$response = $this->callWorkableApi('jobs', $params);

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

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

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

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

$list = ArrayList::create();
$response = $this->callHttpClient('jobs', $params);
$response = $this->callWorkableApi('jobs', $params);

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

/**
* Wrapper method to configure the RestfulService, make the call, and handle errors
* Sends request to Workable API.
* Should it exceed the rate limit, this is caught and put to sleep until the next interval.
* The interval duration is provided by Workable via a header.
* When its awaken, it will call itself again, this repeats until its complete.
* This returns a json body from the response.
*
* Note: See rate limit docs from Workable https://workable.readme.io/docs/rate-limits
* @param string $url
* @param array $params
* @param string $method
*
* @throws RuntimeException if client is not configured correctly
* @throws ClientException if request fails
*
* @throws RequestException if client is not configured correctly, handles 429 error
* @return array JSON as array
*/
public function callHttpClient(string $url, array $params = [], string $method = 'GET'): array
public function callWorkableApi(string $url, array $params = [], string $method = 'GET'): array
{
try {
$response = $this->httpClient->request($method, $url, ['query' => $params]);
} catch (\RuntimeException $e) {
Injector::inst()->get(LoggerInterface::class)->warning(
'Failed to retrieve valid response from workable',
['exception' => $e]
);

throw $e;
return json_decode($response->getBody(), true);
}
catch(RequestException $e){
if($e->hasResponse()){
$errorResponse = $e->getResponse();
$statusCode = $errorResponse->getStatusCode();

if($statusCode === 429) {
Injector::inst()->get(LoggerInterface::class)->info(
'Rate limit exceeded - sleeping until next interval'
);

$this->sleepUntil($errorResponse->getHeader('X-Rate-Limit-Reset'));

return $this->callWorkableApi($url, $params, $method);
}
else {
Injector::inst()->get(LoggerInterface::class)->warning(
'Failed to retrieve valid response from workable',
['exception' => $e]
);

throw $e;
}
}
}
}

return json_decode($response->getBody(), true);
/**
* Sleeps until the next interval.
* Should the interval header be empty, the script sleeps for 10 seconds - Workable's default interval.
* @param array $resetIntervalHeader
*/
private function sleepUntil($resetIntervalHeader){
$defaultSleepInterval = 10;

if(!empty($resetIntervalHeader)){
time_sleep_until($resetIntervalHeader[0]);
}
else {
sleep($defaultSleepInterval);
}
}

/**
Expand All @@ -177,6 +210,7 @@ public static function flush()
}

/**
* Gets any cached data. If there is no cached data, a blank cache is created.
* @return CacheInterface
*/
public function getCache(): CacheInterface
Expand All @@ -189,6 +223,7 @@ public function getCache(): CacheInterface
}

/**
* Sets the cache.
* @param CacheInterface $cache
* @return self
*/
Expand Down

0 comments on commit a1c5f63

Please sign in to comment.