http2: report sent headers object in client stream dcs

This change improves diagnosis by reporting the headers object that is
actually sent rather than the original input headers in the following
diagnostics channels:
- 'http2.client.stream.created'
- 'http2.client.stream.start'

Signed-off-by: Darshan Sen <raisinten@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/59419
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
This commit is contained in:
Darshan Sen 2025-08-17 10:59:41 +05:30 committed by GitHub
parent f3d248b921
commit 7f3a150388
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 63 additions and 12 deletions

View File

@ -760,7 +760,7 @@ const deprecateWeight = deprecateProperty('weight',
// When a ClientHttp2Session is first created, the socket may not yet be // When a ClientHttp2Session is first created, the socket may not yet be
// connected. If request() is called during this time, the actual request // connected. If request() is called during this time, the actual request
// will be deferred until the socket is ready to go. // will be deferred until the socket is ready to go.
function requestOnConnect(headersList, headersParam, options) { function requestOnConnect(headersList, options) {
const session = this[kSession]; const session = this[kSession];
// At this point, the stream should have already been destroyed during // At this point, the stream should have already been destroyed during
@ -824,7 +824,7 @@ function requestOnConnect(headersList, headersParam, options) {
if (onClientStreamStartChannel.hasSubscribers) { if (onClientStreamStartChannel.hasSubscribers) {
onClientStreamStartChannel.publish({ onClientStreamStartChannel.publish({
stream: this, stream: this,
headers: headersParam, headers: this.sentHeaders,
}); });
} }
} }
@ -1888,7 +1888,7 @@ class ClientHttp2Session extends Http2Session {
} }
} }
const onConnect = reqAsync.bind(requestOnConnect.bind(stream, headersList, headersParam, options)); const onConnect = reqAsync.bind(requestOnConnect.bind(stream, headersList, options));
if (this.connecting) { if (this.connecting) {
if (this[kPendingRequestCalls] !== null) { if (this[kPendingRequestCalls] !== null) {
this[kPendingRequestCalls].push(onConnect); this[kPendingRequestCalls].push(onConnect);
@ -1906,7 +1906,7 @@ class ClientHttp2Session extends Http2Session {
if (onClientStreamCreatedChannel.hasSubscribers) { if (onClientStreamCreatedChannel.hasSubscribers) {
onClientStreamCreatedChannel.publish({ onClientStreamCreatedChannel.publish({
stream, stream,
headers: headersParam, headers: stream.sentHeaders,
}); });
} }

View File

@ -18,6 +18,9 @@ const { Duplex } = require('stream');
const clientHttp2StreamCreationCount = 2; const clientHttp2StreamCreationCount = 2;
let countdown;
let port;
dc.subscribe('http2.client.stream.created', common.mustCall(({ stream, headers }) => { dc.subscribe('http2.client.stream.created', common.mustCall(({ stream, headers }) => {
// Since ClientHttp2Stream is not exported from any module, this just checks // Since ClientHttp2Stream is not exported from any module, this just checks
// if the stream is an instance of Duplex and the constructor name is // if the stream is an instance of Duplex and the constructor name is
@ -25,6 +28,28 @@ dc.subscribe('http2.client.stream.created', common.mustCall(({ stream, headers }
assert.ok(stream instanceof Duplex); assert.ok(stream instanceof Duplex);
assert.strictEqual(stream.constructor.name, 'ClientHttp2Stream'); assert.strictEqual(stream.constructor.name, 'ClientHttp2Stream');
assert.ok(headers && !Array.isArray(headers) && typeof headers === 'object'); assert.ok(headers && !Array.isArray(headers) && typeof headers === 'object');
if (countdown.remaining === clientHttp2StreamCreationCount) {
// The request stream headers.
assert.deepStrictEqual(headers, {
'__proto__': null,
':method': 'GET',
':authority': `localhost:${port}`,
':scheme': 'http',
':path': '/',
'requestHeader': 'requestValue',
});
} else {
// The push stream headers.
assert.deepStrictEqual(headers, {
'__proto__': null,
':method': 'GET',
':authority': `localhost:${port}`,
':scheme': 'http',
':path': '/',
[http2.sensitiveHeaders]: [],
'pushheader': 'pushValue',
});
}
}, clientHttp2StreamCreationCount)); }, clientHttp2StreamCreationCount));
const server = http2.createServer(); const server = http2.createServer();
@ -32,22 +57,22 @@ server.on('stream', common.mustCall((stream) => {
stream.respond(); stream.respond();
stream.end(); stream.end();
stream.pushStream({}, common.mustSucceed((pushStream) => { stream.pushStream({ 'pushHeader': 'pushValue' }, common.mustSucceed((pushStream) => {
pushStream.respond(); pushStream.respond();
pushStream.end(); pushStream.end();
}, 1)); }, 1));
}, 1)); }, 1));
server.listen(0, common.mustCall(() => { server.listen(0, common.mustCall(() => {
const port = server.address().port; port = server.address().port;
const client = http2.connect(`http://localhost:${port}`); const client = http2.connect(`http://localhost:${port}`);
const countdown = new Countdown(clientHttp2StreamCreationCount, () => { countdown = new Countdown(clientHttp2StreamCreationCount, () => {
client.close(); client.close();
server.close(); server.close();
}); });
const stream = client.request({}); const stream = client.request(['requestHeader', 'requestValue']);
stream.on('response', common.mustCall(() => { stream.on('response', common.mustCall(() => {
countdown.dec(); countdown.dec();
})); }));

View File

@ -18,12 +18,38 @@ const { Duplex } = require('stream');
const clientHttp2StreamStartCount = 2; const clientHttp2StreamStartCount = 2;
let countdown;
let port;
dc.subscribe('http2.client.stream.start', common.mustCall(({ stream, headers }) => { dc.subscribe('http2.client.stream.start', common.mustCall(({ stream, headers }) => {
// Since ClientHttp2Stream is not exported from any module, this just checks // Since ClientHttp2Stream is not exported from any module, this just checks
// if the stream is an instance of Duplex. // if the stream is an instance of Duplex.
assert.ok(stream instanceof Duplex); assert.ok(stream instanceof Duplex);
assert.strictEqual(stream.constructor.name, 'ClientHttp2Stream'); assert.strictEqual(stream.constructor.name, 'ClientHttp2Stream');
assert.ok(headers && !Array.isArray(headers) && typeof headers === 'object'); assert.ok(headers && !Array.isArray(headers) && typeof headers === 'object');
if (countdown.remaining === clientHttp2StreamStartCount) {
// The request stream headers.
assert.deepStrictEqual(headers, {
'__proto__': null,
':method': 'GET',
':authority': `localhost:${port}`,
':scheme': 'http',
':path': '/',
'requestHeader': 'requestValue',
});
} else {
// The push stream headers.
assert.deepStrictEqual(headers, {
'__proto__': null,
':method': 'GET',
':authority': `localhost:${port}`,
':scheme': 'http',
':path': '/',
[http2.sensitiveHeaders]: [],
'pushheader': 'pushValue',
});
}
}, clientHttp2StreamStartCount)); }, clientHttp2StreamStartCount));
const server = http2.createServer(); const server = http2.createServer();
@ -31,22 +57,22 @@ server.on('stream', common.mustCall((stream) => {
stream.respond(); stream.respond();
stream.end(); stream.end();
stream.pushStream({}, common.mustSucceed((pushStream) => { stream.pushStream({ 'pushHeader': 'pushValue' }, common.mustSucceed((pushStream) => {
pushStream.respond(); pushStream.respond();
pushStream.end(); pushStream.end();
}, 1)); }, 1));
}, 1)); }, 1));
server.listen(0, common.mustCall(() => { server.listen(0, common.mustCall(() => {
const port = server.address().port; port = server.address().port;
const client = http2.connect(`http://localhost:${port}`); const client = http2.connect(`http://localhost:${port}`);
const countdown = new Countdown(clientHttp2StreamStartCount, () => { countdown = new Countdown(clientHttp2StreamStartCount, () => {
client.close(); client.close();
server.close(); server.close();
}); });
const stream = client.request({}); const stream = client.request(['requestHeader', 'requestValue']);
stream.on('response', common.mustCall(() => { stream.on('response', common.mustCall(() => {
countdown.dec(); countdown.dec();
})); }));