Commit b823c3a21e1 for nodejs
commit b823c3a21e1a9143964144e2e292c8aaddf634f8
Author: Roberto Bianchi <rbianchidev@gmail.com>
Date: Sat Oct 10 15:02:45 2026 +0200
http2: speed up response getHeaders
Copy validated header names directly into the shallow result instead
of using ObjectAssign on the internal null-prototype map.
Signed-off-by: Roberto Bianchi <roberto.bianchi@spendesk.com>
PR-URL: https://github.com/nodejs/node/pull/66592
Reviewed-By: Tim Perry <pimterry@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
diff --git a/benchmark/http2/get-headers.js b/benchmark/http2/get-headers.js
new file mode 100644
index 00000000000..a0732d92d7c
--- /dev/null
+++ b/benchmark/http2/get-headers.js
@@ -0,0 +1,46 @@
+'use strict';
+
+const common = require('../common.js');
+const assert = require('node:assert');
+const http2 = require('node:http2');
+
+const bench = common.createBenchmark(main, {
+ n: [1e6],
+ headers: [0, 1, 4, 16, 64],
+});
+
+function main({ n, headers }) {
+ const server = http2.createServer();
+ server.on('request', (request, response) => {
+ const expected = { __proto__: null };
+ for (let index = 0; index < headers; index++) {
+ const name = `x-header-${index}`;
+ response.setHeader(name, 'value');
+ expected[name] = 'value';
+ }
+
+ for (let index = 0; index < 1e4; index++) {
+ response.getHeaders();
+ }
+
+ let result;
+ bench.start();
+ for (let index = 0; index < n; index++) {
+ result = response.getHeaders();
+ }
+ bench.end(n);
+ assert.deepStrictEqual(result, expected);
+ response.end();
+ });
+
+ server.listen(0, '127.0.0.1', () => {
+ const client = http2.connect(`http://127.0.0.1:${server.address().port}`);
+ const request = client.request();
+ request.resume();
+ request.on('end', () => {
+ client.close();
+ server.close();
+ });
+ request.end();
+ });
+}
diff --git a/lib/internal/http2/compat.js b/lib/internal/http2/compat.js
index 17cb76046ee..85abfa714fa 100644
--- a/lib/internal/http2/compat.js
+++ b/lib/internal/http2/compat.js
@@ -634,7 +634,13 @@ class Http2ServerResponse extends Stream {
getHeaders() {
const headers = { __proto__: null };
- return ObjectAssign(headers, this[kHeaders]);
+ const source = this[kHeaders];
+ const keys = ObjectKeys(source);
+ for (let index = 0; index < keys.length; index++) {
+ const key = keys[index];
+ headers[key] = source[key];
+ }
+ return headers;
}
hasHeader(name) {
diff --git a/test/parallel/test-http2-compat-get-headers.js b/test/parallel/test-http2-compat-get-headers.js
new file mode 100644
index 00000000000..f98ceca1db1
--- /dev/null
+++ b/test/parallel/test-http2-compat-get-headers.js
@@ -0,0 +1,80 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('node:assert');
+const http2 = require('node:http2');
+
+const server = http2.createServer(common.mustCall((request, response) => {
+ const empty = response.getHeaders();
+ assert.strictEqual(Object.getPrototypeOf(empty), null);
+ assert.deepStrictEqual(Object.keys(empty), []);
+ assert.notStrictEqual(response.getHeaders(), empty);
+
+ const cookies = ['first=value', 'second=value'];
+ response.setHeader('X-First', 'first');
+ response.setHeader('10', 'ten');
+ response.setHeader('2', 'two');
+ response.setHeader('__proto__', 'proto');
+ response.setHeader('Constructor', 'constructor');
+ response.setHeader('Set-Cookie', cookies);
+
+ const expected = {
+ '__proto__': null,
+ '2': 'two',
+ '10': 'ten',
+ 'x-first': 'first',
+ ['__proto__']: 'proto',
+ 'constructor': 'constructor',
+ 'set-cookie': cookies,
+ };
+ const headers = response.getHeaders();
+ assert.deepStrictEqual(headers, expected);
+ assert.deepStrictEqual(Object.keys(headers), Object.keys(expected));
+ assert.strictEqual(headers['set-cookie'], cookies);
+ assert.notStrictEqual(response.getHeaders(), headers);
+
+ headers['x-first'] = 'changed';
+ headers['x-new'] = 'new';
+ delete headers['2'];
+ assert.deepStrictEqual(response.getHeaders(), expected);
+
+ headers['set-cookie'].push('third=value');
+ assert.strictEqual(response.getHeader('set-cookie'), cookies);
+ assert.strictEqual(response.getHeader('set-cookie').length, 3);
+
+ response.removeHeader('X-First');
+ response.setHeader('X-First', 'replacement');
+ delete expected['x-first'];
+ expected['x-first'] = 'replacement';
+ assert.deepStrictEqual(response.getHeaders(), expected);
+ assert.deepStrictEqual(Object.keys(response.getHeaders()), Object.keys(expected));
+
+ for (const name of response.getHeaderNames()) {
+ response.removeHeader(name);
+ }
+ assert.deepStrictEqual(response.getHeaders(), empty);
+ assert.notStrictEqual(response.getHeaders(), empty);
+
+ response.setHeader('X-Sent', 'value');
+ response.flushHeaders();
+ assert.deepStrictEqual(response.getHeaders(), {
+ '__proto__': null,
+ 'x-sent': 'value',
+ ':status': 200,
+ });
+ response.end();
+}));
+
+server.listen(0, common.mustCall(() => {
+ const client = http2.connect(`http://localhost:${server.address().port}`);
+ const request = client.request();
+ request.resume();
+ request.on('end', common.mustCall(() => {
+ client.close();
+ server.close();
+ }));
+ request.end();
+}));