Skip to content

Commit 1d86261

Browse files
Julien GilliBethGriggs
Julien Gilli
authored andcommittedMar 20, 2019
http: attach reused parser to correct domain
Reused parsers can be attached to the domain that corresponds to the active domain when the underlying socket was created, which is not necessarily correct. Instead, we attach parsers to the active domain if there is one when they're reused from the pool. Refs: #25456 PR-URL: #25459 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
1 parent d6ffabc commit 1d86261

File tree

2 files changed

+115
-6
lines changed

2 files changed

+115
-6
lines changed
 

‎lib/_http_client.js

+3-6
Original file line numberDiff line numberDiff line change
@@ -509,12 +509,6 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
509509
var socket = this.socket;
510510
var req = socket._httpMessage;
511511

512-
// propagate "domain" setting...
513-
if (req.domain && !res.domain) {
514-
debug('setting "res.domain"');
515-
res.domain = req.domain;
516-
}
517-
518512
debug('AGENT incoming response!');
519513

520514
if (req.res) {
@@ -629,6 +623,9 @@ function tickOnSocket(req, socket) {
629623
req.socket = socket;
630624
req.connection = socket;
631625
parser.reinitialize(HTTPParser.RESPONSE, parser[is_reused_symbol]);
626+
if (process.domain) {
627+
process.domain.add(parser);
628+
}
632629
parser.socket = socket;
633630
parser.incoming = null;
634631
parser.outgoing = req;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const domain = require('domain');
5+
const http = require('http');
6+
7+
const agent = new http.Agent({
8+
// keepAlive is important here so that the underlying socket of all requests
9+
// is reused and tied to the same domain.
10+
keepAlive: true
11+
});
12+
13+
const reqSockets = new Set();
14+
15+
function performHttpRequest(opts, cb) {
16+
const req = http.get({ port: server.address().port, agent }, (res) => {
17+
if (!req.socket) {
18+
console.log('missing socket property on req object, failing test');
19+
markTestAsFailed();
20+
req.abort();
21+
cb(new Error('req.socket missing'));
22+
return;
23+
}
24+
25+
reqSockets.add(req.socket);
26+
27+
// Consume the data from the response to make sure the 'end' event is
28+
// emitted.
29+
res.on('data', function noop() {});
30+
res.on('end', () => {
31+
if (opts.shouldThrow) {
32+
throw new Error('boom');
33+
} else {
34+
cb();
35+
}
36+
});
37+
38+
res.on('error', (resErr) => {
39+
console.log('got error on response, failing test:', resErr);
40+
markTestAsFailed();
41+
});
42+
43+
}).on('error', (reqErr) => {
44+
console.log('got error on request, failing test:', reqErr);
45+
markTestAsFailed();
46+
});
47+
48+
req.end();
49+
}
50+
51+
function performHttpRequestInNewDomain(opts, cb) {
52+
const d = domain.create();
53+
d.run(() => {
54+
performHttpRequest(opts, cb);
55+
});
56+
57+
return d;
58+
}
59+
60+
const server = http.createServer((req, res) => {
61+
res.end();
62+
});
63+
64+
server.listen(0, common.mustCall(function() {
65+
const d1 = performHttpRequestInNewDomain({
66+
shouldThrow: false
67+
}, common.mustCall((firstReqErr) => {
68+
if (firstReqErr) {
69+
markTestAsFailed();
70+
return;
71+
}
72+
// We want to schedule the second request on the next turn of the event
73+
// loop so that the parser from the first request is actually reused.
74+
setImmediate(common.mustCall(() => {
75+
const d2 = performHttpRequestInNewDomain({
76+
shouldThrow: true
77+
}, (secondReqErr) => {
78+
// Since this request throws before its callback has the chance
79+
// to be called, we mark the test as failed if this callback is
80+
// called.
81+
console.log('second request\'s cb called unexpectedly, failing test');
82+
markTestAsFailed();
83+
});
84+
85+
// The second request throws when its response's end event is
86+
// emitted. So we expect its domain to emit an error event.
87+
d2.on('error', common.mustCall((d2err) => {
88+
server.close();
89+
if (reqSockets.size !== 1) {
90+
console.log('more than 1 socket used for both requests, failing ' +
91+
'test');
92+
markTestAsFailed();
93+
}
94+
}, 1));
95+
}));
96+
}));
97+
98+
d1.on('error', (d1err) => {
99+
// d1 is the domain attached to the first request, which doesn't throw,
100+
// so we don't expect its error handler to be called.
101+
console.log('first request\'s domain\'s error handler called, ' +
102+
'failing test');
103+
markTestAsFailed();
104+
});
105+
}));
106+
107+
function markTestAsFailed() {
108+
if (server) {
109+
server.close();
110+
}
111+
process.exitCode = 1;
112+
}

0 commit comments

Comments
 (0)
Please sign in to comment.