diff --git a/lib/_http_client.js b/lib/_http_client.js index 891da4e3f9a984..8fdcabaa2a8fd0 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -741,10 +741,24 @@ function socketErrorListener(err) { debug('SOCKET ERROR:', err.message, err.stack); if (req) { + const res = req.res; + const isUserDestroyError = err === req[kError] || err === res?.errored; + // For Safety. Some additional errors might fire later on // and we need to make sure we don't double-fire the error event. socket._hadError = true; - emitErrorEvent(req, err); + // Before a response exists, the request itself failed. Once a response + // exists, socket teardown belongs to the IncomingMessage and is finalized + // by socketCloseListener. Preserve errors explicitly used to destroy the + // request or response, which have historically been emitted on the request. + if (!res || isUserDestroyError) { + emitErrorEvent(req, err); + } + + if (res) { + socket.destroy(); + return; + } } const parser = socket.parser; diff --git a/test/parallel/test-http-client-complete-response-reset.js b/test/parallel/test-http-client-complete-response-reset.js new file mode 100644 index 00000000000000..aa9337dbcc5206 --- /dev/null +++ b/test/parallel/test-http-client-complete-response-reset.js @@ -0,0 +1,55 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +const BODY = Buffer.alloc(2 * 1024 * 1024); + +const server = http.createServer(common.mustCall((request, response) => { + let received = 0; + + request.on('data', function onData(chunk) { + received += chunk.length; + request.off('data', onData); + request.destroy(); + }); + + response.writeHead(413, { 'content-length': 0 }); + response.end(); + + request.on('end', common.mustNotCall()); + request.on('close', common.mustCall(() => { + assert.strictEqual(request.complete, false); + assert.ok(received > 0); + })); +})); + +server.listen(0, common.mustCall(() => { + let response; + const req = http.request({ + method: 'POST', + port: server.address().port, + headers: { + 'content-type': 'application/json', + 'content-length': BODY.length, + }, + }, common.mustCall((res) => { + response = res; + assert.strictEqual(res.statusCode, 413); + // Deliberately do not consume the response body. A response that has + // already been received should not be followed by a late ClientRequest + // socket error. + req.write(BODY); + req.end(); + })); + + req.on('error', common.mustNotCall()); + req.on('close', common.mustCall(() => { + assert(response); + assert.strictEqual(response.complete, true); + server.close(); + })); + + req.flushHeaders(); +}));