Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v8.x] http: attach reused parser to correct domain #25459

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 3 additions & 6 deletions lib/_http_client.js
Expand Up @@ -509,12 +509,6 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
var socket = this.socket;
var req = socket._httpMessage;

// propagate "domain" setting...
if (req.domain && !res.domain) {
debug('setting "res.domain"');
res.domain = req.domain;
}

debug('AGENT incoming response!');

if (req.res) {
Expand Down Expand Up @@ -629,6 +623,9 @@ function tickOnSocket(req, socket) {
req.socket = socket;
req.connection = socket;
parser.reinitialize(HTTPParser.RESPONSE, parser[is_reused_symbol]);
if (process.domain) {
process.domain.add(parser);
}
parser.socket = socket;
parser.incoming = null;
parser.outgoing = req;
Expand Down
80 changes: 80 additions & 0 deletions test/parallel/test-http-parser-reuse-domain.js
@@ -0,0 +1,80 @@
'use strict';

const common = require('../common');
const domain = require('domain');
const http = require('http');

const agent = new http.Agent({
// keepAlive is important here so that the underlying socket of all requests
// is reused and tied to the same domain.
keepAlive: true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have an assertion that checks that the connection is the same for all requests?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point!

Instead of an assertion, which can throw an exception and thus trigger a different code path with regards to domain error handling that would make this test really difficult to write, do you mind if, as for other failure cases in this test, we set the exit code to 1?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@misterdjules Yes, that sounds good to me as well :)

});

function performHttpRequest(opts, cb) {
const req = http.get({ port: server.address().port, agent }, (res) => {
// Consume the data from the response to make sure the 'end' event is
// emitted.
res.on('data', function noop() {});
res.on('end', () => {
if (opts.shouldThrow) {
throw new Error('boom');
} else {
cb();
}
});

res.on('error', (resErr) => {
process.exit(1);
});

}).on('error', (reqErr) => {
process.exit(1);
});

req.end();
}

function performHttpRequestInNewDomain(opts, cb) {
const d = domain.create();
d._id = opts.id;
d.run(() => {
performHttpRequest(opts, cb);
});

return d;
}

const server = http.createServer((req, res) => {
res.end();
});

server.listen(0, common.mustCall(function() {
const d1 = performHttpRequestInNewDomain({
shouldThrow: false
}, common.mustCall((firstReqErr) => {
// We want to schedule the second request on the next turn of the event
// loop so that the parser from the first request is actually reused.
setImmediate(common.mustCall(() => {
const d2 = performHttpRequestInNewDomain({
shouldThrow: true
}, (secondReqErr) => {
// Since this request throws before its callback has the chance
// to be called, we mark the test as failed if this callback is
// called.
process.exit(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the only reason for this is to mark the test as failed, I’d prefer setting process.exitCode = 1 and letting the test finish on its own (here and elsewhere) – it might also be nice to get some output from the test in those cases?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, i'll do both!

});

// The second request throws when its response's end event is
// emitted. So we expect its domain to emit an error event.
d2.on('error', common.mustCall((d2err) => {
server.close();
}, 1));
}));
}));

d1.on('error', (d1err) => {
// d1 is the domain attached to the first request, which doesn't throw,
// so we don't expect its error handler to be called.
process.exit(1);
});
}));