diff --git a/lib/_http_client.js b/lib/_http_client.js index cab11ae8f296c0..d351ded05518d8 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -321,7 +321,7 @@ function socketCloseListener() { var parser = socket.parser; if (req.res && req.res.readable) { // Socket closed before we emitted 'end' below. - req.res.emit('aborted'); + if (!req.res.complete) req.res.emit('aborted'); var res = req.res; res.on('end', function() { res.emit('close'); diff --git a/test/parallel/test-http-client-spurious-aborted.js b/test/parallel/test-http-client-spurious-aborted.js new file mode 100644 index 00000000000000..58a2f92de94054 --- /dev/null +++ b/test/parallel/test-http-client-spurious-aborted.js @@ -0,0 +1,75 @@ +'use strict'; + +const common = require('../common'); +const http = require('http'); +const assert = require('assert'); +const { Writable } = require('stream'); +const Countdown = require('../common/countdown'); + +const N = 2; +let abortRequest = true; + +const server = http.Server(common.mustCall((req, res) => { + const headers = { 'Content-Type': 'text/plain' }; + headers['Content-Length'] = 50; + const socket = res.socket; + res.writeHead(200, headers); + res.write('aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd'); + if (abortRequest) { + process.nextTick(() => socket.destroy()); + } else { + process.nextTick(() => res.end('eeeeeeeeee')); + } +}, N)); + +server.listen(0, common.mustCall(() => { + download(); +})); + +const finishCountdown = new Countdown(N, common.mustCall(() => { + server.close(); +})); +const reqCountdown = new Countdown(N, common.mustCall()); + +function download() { + const opts = { + port: server.address().port, + path: '/', + }; + const req = http.get(opts); + req.on('error', common.mustNotCall()); + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 200); + assert.strictEqual(res.headers.connection, 'close'); + let aborted = false; + const writable = new Writable({ + write(chunk, encoding, callback) { + callback(); + } + }); + res.pipe(writable); + const _handle = res.socket._handle; + _handle._close = res.socket._handle.close; + _handle.close = function(callback) { + _handle._close(); + // set readable to true even though request is complete + if (res.complete) res.readable = true; + callback(); + }; + res.on('end', common.mustCall(() => { + reqCountdown.dec(); + })); + res.on('aborted', () => { + aborted = true; + }); + res.on('error', common.mustNotCall()); + writable.on('finish', () => { + assert.strictEqual(aborted, abortRequest); + finishCountdown.dec(); + if (finishCountdown.remaining === 0) return; + abortRequest = false; // next one should be a good response + download(); + }); + }); + req.end(); +}