Skip to content

Commit b823c3a

Browse files
authored
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: #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>
1 parent 17fa5de commit b823c3a

3 files changed

Lines changed: 133 additions & 1 deletion

File tree

‎benchmark/http2/get-headers.js‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const assert = require('node:assert');
5+
const http2 = require('node:http2');
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [1e6],
9+
headers: [0, 1, 4, 16, 64],
10+
});
11+
12+
function main({ n, headers }) {
13+
const server = http2.createServer();
14+
server.on('request', (request, response) => {
15+
const expected = { __proto__: null };
16+
for (let index = 0; index < headers; index++) {
17+
const name = `x-header-${index}`;
18+
response.setHeader(name, 'value');
19+
expected[name] = 'value';
20+
}
21+
22+
for (let index = 0; index < 1e4; index++) {
23+
response.getHeaders();
24+
}
25+
26+
let result;
27+
bench.start();
28+
for (let index = 0; index < n; index++) {
29+
result = response.getHeaders();
30+
}
31+
bench.end(n);
32+
assert.deepStrictEqual(result, expected);
33+
response.end();
34+
});
35+
36+
server.listen(0, '127.0.0.1', () => {
37+
const client = http2.connect(`http://127.0.0.1:${server.address().port}`);
38+
const request = client.request();
39+
request.resume();
40+
request.on('end', () => {
41+
client.close();
42+
server.close();
43+
});
44+
request.end();
45+
});
46+
}

‎lib/internal/http2/compat.js‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,13 @@ class Http2ServerResponse extends Stream {
634634

635635
getHeaders() {
636636
const headers = { __proto__: null };
637-
return ObjectAssign(headers, this[kHeaders]);
637+
const source = this[kHeaders];
638+
const keys = ObjectKeys(source);
639+
for (let index = 0; index < keys.length; index++) {
640+
const key = keys[index];
641+
headers[key] = source[key];
642+
}
643+
return headers;
638644
}
639645

640646
hasHeader(name) {
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
const assert = require('node:assert');
8+
const http2 = require('node:http2');
9+
10+
const server = http2.createServer(common.mustCall((request, response) => {
11+
const empty = response.getHeaders();
12+
assert.strictEqual(Object.getPrototypeOf(empty), null);
13+
assert.deepStrictEqual(Object.keys(empty), []);
14+
assert.notStrictEqual(response.getHeaders(), empty);
15+
16+
const cookies = ['first=value', 'second=value'];
17+
response.setHeader('X-First', 'first');
18+
response.setHeader('10', 'ten');
19+
response.setHeader('2', 'two');
20+
response.setHeader('__proto__', 'proto');
21+
response.setHeader('Constructor', 'constructor');
22+
response.setHeader('Set-Cookie', cookies);
23+
24+
const expected = {
25+
'__proto__': null,
26+
'2': 'two',
27+
'10': 'ten',
28+
'x-first': 'first',
29+
['__proto__']: 'proto',
30+
'constructor': 'constructor',
31+
'set-cookie': cookies,
32+
};
33+
const headers = response.getHeaders();
34+
assert.deepStrictEqual(headers, expected);
35+
assert.deepStrictEqual(Object.keys(headers), Object.keys(expected));
36+
assert.strictEqual(headers['set-cookie'], cookies);
37+
assert.notStrictEqual(response.getHeaders(), headers);
38+
39+
headers['x-first'] = 'changed';
40+
headers['x-new'] = 'new';
41+
delete headers['2'];
42+
assert.deepStrictEqual(response.getHeaders(), expected);
43+
44+
headers['set-cookie'].push('third=value');
45+
assert.strictEqual(response.getHeader('set-cookie'), cookies);
46+
assert.strictEqual(response.getHeader('set-cookie').length, 3);
47+
48+
response.removeHeader('X-First');
49+
response.setHeader('X-First', 'replacement');
50+
delete expected['x-first'];
51+
expected['x-first'] = 'replacement';
52+
assert.deepStrictEqual(response.getHeaders(), expected);
53+
assert.deepStrictEqual(Object.keys(response.getHeaders()), Object.keys(expected));
54+
55+
for (const name of response.getHeaderNames()) {
56+
response.removeHeader(name);
57+
}
58+
assert.deepStrictEqual(response.getHeaders(), empty);
59+
assert.notStrictEqual(response.getHeaders(), empty);
60+
61+
response.setHeader('X-Sent', 'value');
62+
response.flushHeaders();
63+
assert.deepStrictEqual(response.getHeaders(), {
64+
'__proto__': null,
65+
'x-sent': 'value',
66+
':status': 200,
67+
});
68+
response.end();
69+
}));
70+
71+
server.listen(0, common.mustCall(() => {
72+
const client = http2.connect(`http://localhost:${server.address().port}`);
73+
const request = client.request();
74+
request.resume();
75+
request.on('end', common.mustCall(() => {
76+
client.close();
77+
server.close();
78+
}));
79+
request.end();
80+
}));

0 commit comments

Comments
 (0)