Enhancement: Better Rate Limit Handling & Exposing Rate Limit Headers
vincentsch opened this issue · 2 comments
The client doesn't effectively handle rate limit scenarios or make available important rate-limiting headers. Improved handling or increased transparency is vital given the rate limit restrictions present in APIs.
Current Behavior:
Upon exceeding the rate limit, the client throws a HelpScout\Api\Exception\RateLimitExceededException
. However, it's challenging to discern when the rate limit will reset or gauge how close the client is to reaching the rate limit.
Desired Enhancements:
-
Expose Rate Limit Headers: The Help Scout API provides essential headers related to rate limits:
X-RateLimit-Limit-Minute
: Maximum number of requests per interval.X-RateLimit-Remaining-Minute
: Number of requests remaining in the current rate limit interval.X-RateLimit-Retry-After
: Time (in seconds) to wait until the rate limit refreshes.
Making these headers accessible when using the client, particularly when handling the
RateLimitExceededException
, would be beneficial. -
Built-In Rate Limit Handling: Consider implementing built-in rate limit handling in the client. For instance, on encountering a rate limit error, the client could automatically wait (using the value from
X-RateLimit-Retry-After
) and then retry the request.
Workaround:
For developers currently facing this issue, here's a workaround to extract rate-limiting headers and retry the request:
try {
$response = $client->conversations()->list(/* ... your request parameters ... */);
} catch (ClientException $exception) {
// Extracting rate-limiting headers
$limit = $exception->getResponse()->getHeader('X-RateLimit-Limit-Minute')[0];
$remaining = $exception->getResponse()->getHeader('X-RateLimit-Remaining-Minute')[0];
$retryAfter = $exception->getResponse()->getHeader('X-RateLimit-Retry-After')[0];
echo "Limit: $limit, Remaining: $remaining, Retry After: $retryAfter seconds\n";
// Sleeping for the required time before retrying
sleep($retryAfter);
// Attempt to retry the request
try {
$response = $client->conversations()->list(/* ... your request parameters ... */);
} catch (Exception $retryException) {
echo "Error after retry: " . $retryException->getMessage();
}
}
This workaround is considered a temporary measure, and native support in the client wrapper would be the optimal solution.
Benefits:
- Resilience: Reduced susceptibility to application crashes due to rate limits.
- Efficiency: Native support would alleviate developers from crafting custom rate limit handlers.
- Transparency: Accessible rate limit headers would empower developers to utilize the client more effectively.
Update
Here is the solution I am currently working with:
I added this method to my class:
protected function requestWithRateLimitHandling(callable $requestCallback)
{
while (true) {
try {
return $requestCallback();
} catch (\HelpScout\Api\Exception\RateLimitExceededException $exception) {
$limit = $exception->getResponse()->getHeader('X-RateLimit-Limit-Minute')[0];
$remaining = $exception->getResponse()->getHeader('X-RateLimit-Remaining-Minute')[0];
$retryAfter = $exception->getResponse()->getHeader('X-RateLimit-Retry-After')[0];
$this->info("Rate limit reached. Limit: $limit, Remaining: $remaining, Retry After: $retryAfter seconds");
sleep($retryAfter);
} catch (\Exception $e) {
$this->error($e->getMessage());
return null;
}
}
}
And then I wrap my actual requests like this:
$conversations = $this->requestWithRateLimitHandling(function () use ($filters, $request, $client) {
return $client->conversations()->list($filters, $request);
});
$conversations = $this->requestWithRateLimitHandling(function () use ($conversations) {
return $conversations->getNextPage();
});
@vincentsch Thanks a lot for opening this issue and giving so much information ❤️ We do understand your request and consider it a useful addition to the SDK 👍 We've added a card to our backlog of tickets for this, but I'm afraid we can't make any promises about whether/when we will be able to work on this. We will keep you updated if we make any progress. Thanks!