Skip to content

Commit

Permalink
Add support for RateLimit-Reset header
Browse files Browse the repository at this point in the history
Closes #608

`RateLimit-Reset` and `X-RateLimit-Reset` are used as a fallback for the `Retry-After` header to help support non-standard APIs.
  • Loading branch information
sholladay committed Aug 11, 2024
1 parent fbe0ec6 commit 37b7a9b
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 3 deletions.
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ An object representing `limit`, `methods`, `statusCodes`, `afterStatusCodes`, an

If `retry` is a number, it will be used as `limit` and other defaults will remain in place.

If the response provides an HTTP status contained in `afterStatusCodes`, Ky will wait until the date or timeout given in the [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header has passed to retry the request. If the provided status code is not in the list, the [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header will be ignored.
If the response provides an HTTP status contained in `afterStatusCodes`, Ky will wait until the date or timeout given in the [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header has passed to retry the request. If `Retry-After` is missing, the non-standard [`RateLimit-Reset`](https://www.ietf.org/archive/id/draft-polli-ratelimit-headers-02.html#section-3.3) header is used in its place as a fallback. If the provided status code is not in the list, the [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header will be ignored.

If `maxRetryAfter` is set to `undefined`, it will use `options.timeout`. If [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header is greater than `maxRetryAfter`, it will use `maxRetryAfter`.

Expand Down
4 changes: 3 additions & 1 deletion source/core/Ky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@ export class Ky {
throw error;
}

const retryAfter = error.response.headers.get('Retry-After');
const retryAfter = error.response.headers.get('Retry-After')
?? error.response.headers.get('RateLimit-Reset')
?? error.response.headers.get('X-RateLimit-Reset');
if (retryAfter && this._options.retry.afterStatusCodes.includes(error.response.status)) {
let after = Number(retryAfter) * 1000;
if (Number.isNaN(after)) {
Expand Down
2 changes: 1 addition & 1 deletion source/types/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export type KyOptions = {
If `retry` is a number, it will be used as `limit` and other defaults will remain in place.
If the response provides an HTTP status contained in `afterStatusCodes`, Ky will wait until the date or timeout given in the [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header has passed to retry the request. If the provided status code is not in the list, the [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header will be ignored.
If the response provides an HTTP status contained in `afterStatusCodes`, Ky will wait until the date or timeout given in the [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header has passed to retry the request. If `Retry-After` is missing, the non-standard [`RateLimit-Reset`](https://www.ietf.org/archive/id/draft-polli-ratelimit-headers-02.html#section-3.3) header is used in its place as a fallback. If the provided status code is not in the list, the [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header will be ignored.
If `maxRetryAfter` is set to `undefined`, it will use `options.timeout`. If [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header is greater than `maxRetryAfter`, it will cancel the request.
Expand Down
32 changes: 32 additions & 0 deletions test/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,38 @@ test('respect Retry-After: 0 and retry immediately', async t => {
await server.close();
});

test('RateLimit-Reset is treated the same as Retry-After', async t => {
let requestCount = 0;

const server = await createHttpTestServer();
server.get('/', (_request, response) => {
requestCount++;

if (requestCount === defaultRetryCount + 1) {
response.end(fixture);
} else {
const header = (requestCount < 2) ? 'RateLimit-Reset' : 'Retry-After';
response.writeHead(429, {
[header]: 1,
});

response.end('');
}
});

await withPerformance({
t,
expectedDuration: 1000 + 1000,
async test() {
t.is(await ky(server.url).text(), fixture);
},
});

t.is(requestCount, 3);

await server.close();
});

test('respect 413 Retry-After', async t => {
const startTime = Date.now();
let requestCount = 0;
Expand Down

0 comments on commit 37b7a9b

Please sign in to comment.