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();
+}));