From e5504e8e8591e18232a7708cdb99df23b4d10113 Mon Sep 17 00:00:00 2001 From: Francesco Giovannini Date: Thu, 6 Mar 2025 18:01:25 +0100 Subject: [PATCH] fix: Fix retry-handler.js when retry-after header is a Date --- lib/handler/retry-handler.js | 6 +-- test/interceptors/retry.js | 6 +-- test/retry-handler.js | 92 ++++++++++++++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 9 deletions(-) diff --git a/lib/handler/retry-handler.js b/lib/handler/retry-handler.js index d929b0c2ae0..00f582e570f 100644 --- a/lib/handler/retry-handler.js +++ b/lib/handler/retry-handler.js @@ -11,8 +11,8 @@ const { } = require('../core/util') function calculateRetryAfterHeader (retryAfter) { - const current = Date.now() - return new Date(retryAfter).getTime() - current + const retryTime = new Date(retryAfter).getTime() + return isNaN(retryTime) ? 0 : retryTime - Date.now() } class RetryHandler { @@ -124,7 +124,7 @@ class RetryHandler { if (retryAfterHeader) { retryAfterHeader = Number(retryAfterHeader) retryAfterHeader = Number.isNaN(retryAfterHeader) - ? calculateRetryAfterHeader(retryAfterHeader) + ? calculateRetryAfterHeader(headers['retry-after']) : retryAfterHeader * 1e3 // Retry-After is in seconds } diff --git a/test/interceptors/retry.js b/test/interceptors/retry.js index 2307d406914..9ec033d09da 100644 --- a/test/interceptors/retry.js +++ b/test/interceptors/retry.js @@ -191,19 +191,19 @@ test('Should use retry-after header for retries (date)', async t => { server.on('request', (req, res) => { switch (counter) { case 0: + checkpoint = Date.now() res.writeHead(429, { 'retry-after': new Date( - new Date().setSeconds(new Date().getSeconds() + 1) + checkpoint + 2000 ).toUTCString() }) res.end('rate limit') - checkpoint = Date.now() counter++ return case 1: res.writeHead(200) res.end('hello world!') - t.ok(Date.now() - checkpoint >= 1) + t.ok(Date.now() - checkpoint >= 1000) counter++ return default: diff --git a/test/retry-handler.js b/test/retry-handler.js index a52324c086d..85eea6b0597 100644 --- a/test/retry-handler.js +++ b/test/retry-handler.js @@ -364,19 +364,19 @@ test('Should use retry-after header for retries (date)', async t => { server.on('request', (req, res) => { switch (counter) { case 0: + checkpoint = Date.now() res.writeHead(429, { 'retry-after': new Date( - new Date().setSeconds(new Date().getSeconds() + 1) + checkpoint + 2000 ).toUTCString() }) res.end('rate limit') - checkpoint = Date.now() counter++ return case 1: res.writeHead(200) res.end('hello world!') - t.ok(Date.now() - checkpoint >= 1) + t.ok(Date.now() - checkpoint >= 1000) counter++ return default: @@ -1547,3 +1547,89 @@ test('Should throw RequestRetryError when Content-Range mismatch', async t => { await t.completed }) + +test('Should use retry-after header for retries (date) but date format is wrong', async t => { + t = tspl(t, { plan: 3 }) + + let counter = 0 + const chunks = [] + const server = createServer() + let checkpoint + const dispatchOptions = { + method: 'PUT', + path: '/', + headers: { + 'content-type': 'application/json' + }, + retryOptions: { + minTimeout: 1000 + } + } + + server.on('request', (req, res) => { + switch (counter) { + case 0: + checkpoint = Date.now() + res.writeHead(429, { + 'retry-after': 'this is not a date' + }) + res.end('rate limit') + counter++ + return + case 1: + res.writeHead(200) + res.end('hello world!') + t.ok(Date.now() - checkpoint >= 1000) + counter++ + return + default: + t.fail('unexpected request') + } + }) + + server.listen(0, () => { + const client = new Client(`http://localhost:${server.address().port}`) + const handler = new RetryHandler(dispatchOptions, { + dispatch: client.dispatch.bind(client), + handler: { + onConnect () { + t.ok(true, 'pass') + }, + onHeaders (status, _rawHeaders, resume, _statusMessage) { + t.strictEqual(status, 200) + return true + }, + onData (chunk) { + chunks.push(chunk) + return true + }, + onComplete () { + t.strictEqual(Buffer.concat(chunks).toString('utf-8'), 'hello world!') + }, + onError (err) { + t.ifError(err) + } + } + }) + + after(async () => { + await client.close() + server.close() + + await once(server, 'close') + }) + + client.dispatch( + { + method: 'PUT', + path: '/', + headers: { + 'content-type': 'application/json' + } + }, + handler + ) + }) + + await t.completed +})