diff --git a/doc/api/http.md b/doc/api/http.md index 10d728a13d4523..3adccd1b127912 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -333,9 +333,8 @@ Until the data is consumed, the `'end'` event will not fire. Also, until the data is read it will consume memory that can eventually lead to a 'process out of memory' error. -Unlike the `request` object, if the response closes prematurely, the -`response` object does not emit an `'error'` event but instead emits the -`'aborted'` event. +For backward compatibility, `res` will only emit `'error'` if there is an +`'error'` listener registered. Node.js does not check whether Content-Length and the length of the body which has been transmitted are equal or not. @@ -2404,6 +2403,8 @@ the following events will be emitted in the following order: * `'data'` any number of times, on the `res` object * (connection closed here) * `'aborted'` on the `res` object +* `'error'` on the `res` object with an error with message + `'Error: socket hang up'` and code `'ECONNRESET'`. * `'close'` * `'close'` on the `res` object @@ -2432,6 +2433,8 @@ events will be emitted in the following order: * `'data'` any number of times, on the `res` object * (`req.destroy()` called here) * `'aborted'` on the `res` object +* `'error'` on the `res` object with an error with message + `'Error: socket hang up'` and code `'ECONNRESET'`. * `'close'` * `'close'` on the `res` object @@ -2461,6 +2464,8 @@ events will be emitted in the following order: * (`req.abort()` called here) * `'abort'` * `'aborted'` on the `res` object +* `'error'` on the `res` object with an error with message + `'Error: socket hang up'` and code `'ECONNRESET'`. * `'close'` * `'close'` on the `res` object diff --git a/lib/_http_client.js b/lib/_http_client.js index 73a6409c411cc1..886489dd939227 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -413,9 +413,13 @@ function socketCloseListener() { const res = req.res; if (res) { // Socket closed before we emitted 'end' below. + // TOOD(ronag): res.destroy(err) if (!res.complete) { res.aborted = true; res.emit('aborted'); + if (res.listenerCount('error') > 0) { + res.emit('error', connResetException('aborted')); + } } req.emit('close'); if (!res.aborted && res.readable) { diff --git a/test/parallel/test-http-abort-client.js b/test/parallel/test-http-abort-client.js index 608d4dc7607853..3664237c53c6e0 100644 --- a/test/parallel/test-http-abort-client.js +++ b/test/parallel/test-http-abort-client.js @@ -21,6 +21,7 @@ 'use strict'; const common = require('../common'); +const assert = require('assert'); const http = require('http'); let serverRes; @@ -41,6 +42,9 @@ server.listen(0, common.mustCall(() => { res.resume(); res.on('end', common.mustNotCall()); res.on('aborted', common.mustCall()); + res.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ECONNRESET'); + })); res.on('close', common.mustCall()); res.socket.on('close', common.mustCall()); })); diff --git a/test/parallel/test-http-aborted.js b/test/parallel/test-http-aborted.js index 44ee106b18df06..ff45469e45e3c2 100644 --- a/test/parallel/test-http-aborted.js +++ b/test/parallel/test-http-aborted.js @@ -25,6 +25,9 @@ const assert = require('assert'); res.on('aborted', common.mustCall(() => { assert.strictEqual(res.aborted, true); })); + res.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ECONNRESET'); + })); req.abort(); })); })); @@ -36,6 +39,7 @@ const assert = require('assert'); const server = http.createServer(common.mustCall(function(req, res) { req.on('aborted', common.mustCall(function() { assert.strictEqual(this.aborted, true); + server.close(); })); assert.strictEqual(req.aborted, false); res.write('hello'); diff --git a/test/parallel/test-http-client-aborted-event.js b/test/parallel/test-http-client-aborted-event.js index 1e7feb7d5860f6..f73672f5db69ec 100644 --- a/test/parallel/test-http-client-aborted-event.js +++ b/test/parallel/test-http-client-aborted-event.js @@ -1,20 +1,46 @@ 'use strict'; const common = require('../common'); const http = require('http'); +const assert = require('assert'); -let serverRes; -const server = http.Server(function(req, res) { - res.write('Part of my res.'); - serverRes = res; -}); +{ + let serverRes; + const server = http.Server(function(req, res) { + res.write('Part of my res.'); + serverRes = res; + }); -server.listen(0, common.mustCall(function() { - http.get({ - port: this.address().port, - headers: { connection: 'keep-alive' } - }, common.mustCall(function(res) { - server.close(); - serverRes.destroy(); - res.on('aborted', common.mustCall()); + server.listen(0, common.mustCall(function() { + http.get({ + port: this.address().port, + headers: { connection: 'keep-alive' } + }, common.mustCall(function(res) { + server.close(); + serverRes.destroy(); + res.on('aborted', common.mustCall()); + res.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ECONNRESET'); + })); + })); })); -})); +} + +{ + // Don't crash of no 'error' handler. + let serverRes; + const server = http.Server(function(req, res) { + res.write('Part of my res.'); + serverRes = res; + }); + + server.listen(0, common.mustCall(function() { + http.get({ + port: this.address().port, + headers: { connection: 'keep-alive' } + }, common.mustCall(function(res) { + server.close(); + serverRes.destroy(); + res.on('aborted', common.mustCall()); + })); + })); +} diff --git a/test/parallel/test-http-client-spurious-aborted.js b/test/parallel/test-http-client-spurious-aborted.js index 0cb2f471c2b792..7809c453a2ae18 100644 --- a/test/parallel/test-http-client-spurious-aborted.js +++ b/test/parallel/test-http-client-spurious-aborted.js @@ -60,15 +60,18 @@ function download() { res.on('end', common.mustCall(() => { reqCountdown.dec(); })); + res.on('error', common.mustNotCall()); } else { res.on('aborted', common.mustCall(() => { aborted = true; reqCountdown.dec(); writable.end(); })); + res.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ECONNRESET'); + })); } - res.on('error', common.mustNotCall()); writable.on('finish', () => { assert.strictEqual(aborted, abortRequest); finishCountdown.dec(); diff --git a/test/parallel/test-http-outgoing-message-capture-rejection.js b/test/parallel/test-http-outgoing-message-capture-rejection.js index 5f667ea17ea156..fbe9f305b52bca 100644 --- a/test/parallel/test-http-outgoing-message-capture-rejection.js +++ b/test/parallel/test-http-outgoing-message-capture-rejection.js @@ -33,6 +33,9 @@ events.captureRejections = true; req.on('response', common.mustCall((res) => { res.on('aborted', common.mustCall()); + res.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ECONNRESET'); + })); res.resume(); server.close(); }));