Skip to content

Commit 299b2fd

Browse files
committed
http: don't emit error after close
Refs: #33591
1 parent 5536044 commit 299b2fd

8 files changed

+71
-28
lines changed

lib/_http_client.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const Agent = require('_http_agent');
4949
const { Buffer } = require('buffer');
5050
const { defaultTriggerAsyncIdScope } = require('internal/async_hooks');
5151
const { URL, urlToOptions, searchParamsSymbol } = require('internal/url');
52-
const { kOutHeaders, kNeedDrain } = require('internal/http');
52+
const { kOutHeaders, kNeedDrain, kClosed } = require('internal/http');
5353
const { connResetException, codes } = require('internal/errors');
5454
const {
5555
ERR_HTTP_HEADERS_SENT,
@@ -385,6 +385,7 @@ function _destroy(req, socket, err) {
385385
if (err) {
386386
req.emit('error', err);
387387
}
388+
req[kClosed] = true;
388389
req.emit('close');
389390
}
390391
}
@@ -427,6 +428,7 @@ function socketCloseListener() {
427428
res.emit('error', connResetException('aborted'));
428429
}
429430
}
431+
req[kClosed] = true;
430432
req.emit('close');
431433
if (!res.aborted && res.readable) {
432434
res.on('end', function() {
@@ -446,6 +448,7 @@ function socketCloseListener() {
446448
req.socket._hadError = true;
447449
req.emit('error', connResetException('socket hang up'));
448450
}
451+
req[kClosed] = true;
449452
req.emit('close');
450453
}
451454

@@ -550,6 +553,7 @@ function socketOnData(d) {
550553

551554
req.emit(eventName, res, socket, bodyHead);
552555
req.destroyed = true;
556+
req[kClosed] = true;
553557
req.emit('close');
554558
} else {
555559
// Requested Upgrade or used CONNECT method, but have no handler.
@@ -721,6 +725,7 @@ function requestOnPrefinish() {
721725
}
722726

723727
function emitFreeNT(req) {
728+
req[kClosed] = true;
724729
req.emit('close');
725730
if (req.res) {
726731
req.res.emit('close');

lib/_http_outgoing.js

+13-4
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const assert = require('internal/assert');
3636
const EE = require('events');
3737
const Stream = require('stream');
3838
const internalUtil = require('internal/util');
39-
const { kOutHeaders, utcDate, kNeedDrain } = require('internal/http');
39+
const { kOutHeaders, utcDate, kNeedDrain, kClosed } = require('internal/http');
4040
const { Buffer } = require('buffer');
4141
const common = require('_http_common');
4242
const checkIsHttpToken = common._checkIsHttpToken;
@@ -117,6 +117,7 @@ function OutgoingMessage() {
117117
this.finished = false;
118118
this._headerSent = false;
119119
this[kCorked] = 0;
120+
this[kClosed] = false;
120121

121122
this.socket = null;
122123
this._header = null;
@@ -663,7 +664,9 @@ function onError(msg, err, callback) {
663664

664665
function emitErrorNt(msg, err, callback) {
665666
callback(err);
666-
if (typeof msg.emit === 'function') msg.emit('error', err);
667+
if (typeof msg.emit === 'function' && !msg[kClosed]) {
668+
msg.emit('error', err);
669+
}
667670
}
668671

669672
function write_(msg, chunk, encoding, callback, fromEnd) {
@@ -690,7 +693,11 @@ function write_(msg, chunk, encoding, callback, fromEnd) {
690693
}
691694

692695
if (err) {
693-
onError(msg, err, callback);
696+
if (!msg.destroyed) {
697+
onError(msg, err, callback);
698+
} else {
699+
process.nextTick(callback, err);
700+
}
694701
return false;
695702
}
696703

@@ -780,7 +787,9 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
780787
}
781788

782789
if (chunk) {
783-
write_(this, chunk, encoding, null, true);
790+
if (!write_(this, chunk, encoding, null, true)) {
791+
return;
792+
}
784793
} else if (this.finished) {
785794
if (typeof callback === 'function') {
786795
if (!this.writableFinished) {

lib/_http_server.js

+3
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const { OutgoingMessage } = require('_http_outgoing');
4949
const {
5050
kOutHeaders,
5151
kNeedDrain,
52+
kClosed,
5253
emitStatistics
5354
} = require('internal/http');
5455
const {
@@ -212,6 +213,7 @@ function onServerResponseClose() {
212213
// Fortunately, that requires only a single if check. :-)
213214
if (this._httpMessage) {
214215
this._httpMessage.destroyed = true;
216+
this._httpMessage[kClosed] = true;
215217
this._httpMessage.emit('close');
216218
}
217219
}
@@ -729,6 +731,7 @@ function resOnFinish(req, res, socket, state, server) {
729731

730732
function emitCloseNT(self) {
731733
self.destroyed = true;
734+
self[kClosed] = true;
732735
self.emit('close');
733736
}
734737

lib/internal/http.js

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ function emitStatistics(statistics) {
5151
module.exports = {
5252
kOutHeaders: Symbol('kOutHeaders'),
5353
kNeedDrain: Symbol('kNeedDrain'),
54+
kClosed: Symbol('kClosed'),
5455
nowDate,
5556
utcDate,
5657
emitStatistics

test/parallel/test-http-outgoing-destroy.js

+1-6
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,8 @@ const OutgoingMessage = http.OutgoingMessage;
1010
assert.strictEqual(msg.destroyed, false);
1111
msg.destroy();
1212
assert.strictEqual(msg.destroyed, true);
13-
let callbackCalled = false;
1413
msg.write('asd', common.mustCall((err) => {
1514
assert.strictEqual(err.code, 'ERR_STREAM_DESTROYED');
16-
callbackCalled = true;
17-
}));
18-
msg.on('error', common.mustCall((err) => {
19-
assert.strictEqual(err.code, 'ERR_STREAM_DESTROYED');
20-
assert.strictEqual(callbackCalled, true);
2115
}));
16+
msg.on('error', common.mustNotCall());
2217
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
const common = require('../common');
3+
const http = require('http');
4+
5+
const server = http.createServer(common.mustCall((req, res) => {
6+
req.pipe(res);
7+
res.on('error', common.mustNotCall());
8+
res.on('close', common.mustCall(() => {
9+
res.end('asd');
10+
process.nextTick(() => {
11+
server.close();
12+
});
13+
}));
14+
})).listen(0, () => {
15+
http
16+
.request({
17+
port: server.address().port,
18+
method: 'PUT'
19+
})
20+
.on('response', (res) => {
21+
res.destroy();
22+
})
23+
.write('asd');
24+
});

test/parallel/test-http-server-write-after-end.js

+8-8
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,19 @@ const http = require('http');
88
const server = http.createServer(handle);
99

1010
function handle(req, res) {
11-
res.on('error', common.mustCall((err) => {
12-
common.expectsError({
13-
code: 'ERR_STREAM_WRITE_AFTER_END',
14-
name: 'Error'
15-
})(err);
16-
server.close();
17-
}));
11+
res.on('error', common.mustNotCall());
1812

1913
res.write('hello');
2014
res.end();
2115

2216
setImmediate(common.mustCall(() => {
23-
res.write('world');
17+
res.write('world', common.mustCall((err) => {
18+
common.expectsError({
19+
code: 'ERR_STREAM_WRITE_AFTER_END',
20+
name: 'Error'
21+
})(err);
22+
server.close();
23+
}));
2424
}));
2525
}
2626

test/parallel/test-http-server-write-end-after-end.js

+15-9
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,26 @@ const http = require('http');
66
const server = http.createServer(handle);
77

88
function handle(req, res) {
9-
res.on('error', common.mustCall((err) => {
10-
common.expectsError({
11-
code: 'ERR_STREAM_WRITE_AFTER_END',
12-
name: 'Error'
13-
})(err);
14-
server.close();
15-
}));
9+
res.on('error', common.mustNotCall());
1610

1711
res.write('hello');
1812
res.end();
1913

20-
setImmediate(common.mustCall(() => {
14+
setImmediate(() => {
15+
// TODO: Known issues. OutgoingMessage does not always invoke callback
16+
// in end. Also streams don't propagate write after end to callback.
2117
res.end('world');
22-
}));
18+
process.nextTick(() => {
19+
server.close();
20+
});
21+
// res.end('world', common.mustCall((err) => {
22+
// common.expectsError({
23+
// code: 'ERR_STREAM_WRITE_AFTER_END',
24+
// name: 'Error'
25+
// })(err);
26+
// server.close();
27+
// }));
28+
});
2329
}
2430

2531
server.listen(0, common.mustCall(() => {

0 commit comments

Comments
 (0)