http2: set Http2Stream#sentHeaders for raw headers

When https://github.com/nodejs/node/pull/57917 added support for sending
raw header arrays, Http2Stream#sentHeaders was set only for header
objects. This change also sets it for raw headers by lazily
instantiating the property to avoid any performance impact on the fast
path.

Signed-off-by: Darshan Sen <raisinten@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/59244
Reviewed-By: Tim Perry <pimterry@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
Darshan Sen 2025-07-29 20:14:52 +05:30 committed by GitHub
parent cde8f275ad
commit 751203d36b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 61 additions and 2 deletions

View file

@ -254,6 +254,7 @@ const kProceed = Symbol('proceed');
const kRemoteSettings = Symbol('remote-settings');
const kRequestAsyncResource = Symbol('requestAsyncResource');
const kSentHeaders = Symbol('sent-headers');
const kRawHeaders = Symbol('raw-headers');
const kSentTrailers = Symbol('sent-trailers');
const kServer = Symbol('server');
const kState = Symbol('state');
@ -1807,12 +1808,14 @@ class ClientHttp2Session extends Http2Session {
let headersList;
let headersObject;
let rawHeaders;
let scheme;
let authority;
let method;
if (ArrayIsArray(headersParam)) {
({
rawHeaders,
headersList,
scheme,
authority,
@ -1859,6 +1862,7 @@ class ClientHttp2Session extends Http2Session {
// eslint-disable-next-line no-use-before-define
const stream = new ClientHttp2Stream(this, undefined, undefined, {});
stream[kSentHeaders] = headersObject; // N.b. Only set for object headers, not raw headers
stream[kRawHeaders] = rawHeaders; // N.b. Only set for raw headers, not object headers
stream[kOrigin] = `${scheme}://${authority}`;
const reqAsync = new AsyncResource('PendingRequest');
stream[kRequestAsyncResource] = reqAsync;
@ -2128,6 +2132,33 @@ class Http2Stream extends Duplex {
}
get sentHeaders() {
if (this[kSentHeaders] || !this[kRawHeaders]) {
return this[kSentHeaders];
}
const rawHeaders = this[kRawHeaders];
const headersObject = { __proto__: null };
for (let i = 0; i < rawHeaders.length; i += 2) {
const key = rawHeaders[i];
const value = rawHeaders[i + 1];
const existing = headersObject[key];
if (existing === undefined) {
headersObject[key] = value;
} else if (ArrayIsArray(existing)) {
existing.push(value);
} else {
headersObject[key] = [existing, value];
}
}
if (rawHeaders[kSensitiveHeaders] !== undefined) {
headersObject[kSensitiveHeaders] = rawHeaders[kSensitiveHeaders];
}
this[kSentHeaders] = headersObject;
return this[kSentHeaders];
}

View file

@ -678,15 +678,23 @@ function prepareRequestHeadersArray(headers, session) {
throw new ERR_HTTP2_CONNECT_PATH();
}
const headersList = buildNgHeaderString(
const rawHeaders =
additionalPsuedoHeaders.length ?
additionalPsuedoHeaders.concat(headers) :
headers,
headers;
if (headers[kSensitiveHeaders] !== undefined) {
rawHeaders[kSensitiveHeaders] = headers[kSensitiveHeaders];
}
const headersList = buildNgHeaderString(
rawHeaders,
assertValidPseudoHeader,
headers[kSensitiveHeaders],
);
return {
rawHeaders,
headersList,
scheme,
authority: authority ?? headers[HTTP2_HEADER_HOST],

View file

@ -39,6 +39,16 @@ const http2 = require('http2');
'a', 'c',
]).end();
assert.deepStrictEqual(req.sentHeaders, {
'__proto__': null,
':path': '/foobar',
':scheme': 'http',
':authority': `localhost:${server.address().port}`,
':method': 'GET',
'a': [ 'b', 'c' ],
'x-FOO': 'bar',
});
req.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[':status'], 200);
client.close();

View file

@ -72,6 +72,16 @@ const { duplexPair } = require('stream');
const req = client.request(rawHeaders);
assert.deepStrictEqual(req.sentHeaders, {
'__proto__': null,
':method': 'GET',
':authority': 'localhost:80',
':scheme': 'http',
':path': '/',
'secret': 'secret-value',
[http2.sensitiveHeaders]: [ 'secret' ],
});
req.on('response', common.mustCall((headers) => {
assert.strictEqual(headers[':status'], 200);
}));