From cd0d44825fe1db7ab597bb139206d780ae94a8ce Mon Sep 17 00:00:00 2001 From: Andrii Furmanets Date: Tue, 28 Apr 2026 16:31:36 +0300 Subject: [PATCH] fix(http): preserve response in error when stream is aborted after headers (#10708) * fix(http): preserve response in error when stream is aborted after headers When a server sends response headers but aborts the stream before completing the body, axios now attaches the response object to the error. This allows consumers to access response metadata (status, headers) for debugging, retry logic, or user messaging. Fixes #6935 * fix(http): normalize response stream errors --------- Co-authored-by: Jay --- lib/adapters/http.js | 5 +++-- tests/unit/adapters/http.test.js | 22 ++++++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/adapters/http.js b/lib/adapters/http.js index f59364aa..e0efc409 100755 --- a/lib/adapters/http.js +++ b/lib/adapters/http.js @@ -904,7 +904,8 @@ export default isHttpAdapterSupported && 'stream has been aborted', AxiosError.ERR_BAD_RESPONSE, config, - lastRequest + lastRequest, + response ); responseStream.destroy(err); reject(err); @@ -912,7 +913,7 @@ export default isHttpAdapterSupported && responseStream.on('error', function handleStreamError(err) { if (req.destroyed) return; - reject(AxiosError.from(err, null, config, lastRequest)); + reject(AxiosError.from(err, null, config, lastRequest, response)); }); responseStream.on('end', function handleStreamEnd() { diff --git a/tests/unit/adapters/http.test.js b/tests/unit/adapters/http.test.js index 5027d0af..19cd631f 100644 --- a/tests/unit/adapters/http.test.js +++ b/tests/unit/adapters/http.test.js @@ -612,17 +612,28 @@ describe('supports http with nodejs', () => { it('should support gunzip error handling', async () => { const server = await startHTTPServer( (req, res) => { + res.statusCode = 206; res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Encoding', 'gzip'); + res.setHeader('X-Stream-Error', 'yes'); res.end('invalid response'); }, { port: SERVER_PORT } ); try { - await assert.rejects(async () => { - await axios.get(`http://localhost:${server.address().port}/`); - }); + await assert.rejects( + async () => { + await axios.get(`http://localhost:${server.address().port}/`); + }, + (error) => { + assert.strictEqual(error.response.status, 206); + assert.strictEqual(error.response.headers.get('x-stream-error'), 'yes'); + assert.strictEqual(error.status, 206); + + return true; + } + ); } finally { await stopHTTPServer(server); } @@ -2692,7 +2703,7 @@ describe('supports http with nodejs', () => { it('should throw an error if http server that aborts a chunked request', async () => { const server = await startHTTPServer( (req, res) => { - res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.writeHead(200, { 'Content-Type': 'text/plain', 'X-Stream-Aborted': 'yes' }); res.write('chunk 1'); setTimeout(() => { @@ -2714,6 +2725,9 @@ describe('supports http with nodejs', () => { (error) => { assert.strictEqual(error.code, 'ERR_BAD_RESPONSE'); assert.strictEqual(error.message, 'stream has been aborted'); + assert.strictEqual(error.response.status, 200); + assert.strictEqual(error.response.headers.get('x-stream-aborted'), 'yes'); + assert.strictEqual(error.status, 200); return true; }