diff --git a/lib/handler/retry-handler.js b/lib/handler/retry-handler.js index 98f73bf9b3b..78fae4c3074 100644 --- a/lib/handler/retry-handler.js +++ b/lib/handler/retry-handler.js @@ -293,18 +293,6 @@ class RetryHandler { this.statusCode = statusCode this.headers = headers - if (statusCode >= 300) { - const err = new RequestRetryError('Request failed', statusCode, { - headers, - data: { - count: this.retryCount - } - }) - - this.onResponseStartWithRetry(controller, statusCode, headers, statusMessage, err) - return - } - // Checkpoint for resume from where we left it if (this.headersSent) { // Only Partial Content 206 supposed to provide Content-Range, @@ -347,6 +335,18 @@ class RetryHandler { return } + if (statusCode >= 300) { + const err = new RequestRetryError('Request failed', statusCode, { + headers, + data: { + count: this.retryCount + } + }) + + this.onResponseStartWithRetry(controller, statusCode, headers, statusMessage, err) + return + } + if (this.end == null) { if (statusCode === 206) { // First time we receive 206 diff --git a/test/retry-agent.js b/test/retry-agent.js index f4edaa5f4a0..a14582e1430 100644 --- a/test/retry-agent.js +++ b/test/retry-agent.js @@ -4,6 +4,7 @@ const { tspl } = require('@matteo.collina/tspl') const { test, after } = require('node:test') const { createServer } = require('node:http') const { once } = require('node:events') +const { Writable } = require('node:stream') const { RetryAgent, Client } = require('..') test('Should retry status code', async t => { @@ -65,3 +66,61 @@ test('Should retry status code', async t => { await t.completed }) + +for (const throwOnError of [true, false]) { + test(`Should reject a non-206 response when resuming a partially consumed response | throwOnError: ${throwOnError}`, async context => { + const t = tspl(context, { plan: 4 }) + + let requestCount = 0 + const chunks = [] + const server = createServer((req, res) => { + if (requestCount++ === 0) { + res.writeHead(200, { 'content-length': '12' }) + res.write('AAAA') + const socket = res.socket + setTimeout(() => socket.destroy(), 50) + return + } + + t.equal(req.headers.range, 'bytes=4-11') + res.writeHead(412, { 'content-length': '8' }) + res.end('XXXXXXXX') + }) + + server.listen(0) + await once(server, 'listening') + + const client = new Client(`http://localhost:${server.address().port}`) + const agent = new RetryAgent(client, { + throwOnError, + maxRetries: 3, + minTimeout: 10, + maxTimeout: 10 + }) + + context.after(async () => { + await agent.close() + server.close() + await once(server, 'close') + }) + + await t.rejects(agent.stream({ + method: 'GET', + path: '/' + }, ({ statusCode }) => { + t.equal(statusCode, 200) + return new Writable({ + write (chunk, encoding, callback) { + chunks.push(chunk) + callback() + } + }) + }), { + code: 'UND_ERR_REQ_RETRY', + message: 'server does not support the range header and the payload was partially consumed' + }) + + t.equal(Buffer.concat(chunks).toString(), 'AAAA') + await t.completed + }) +}