Skip to content

Commit 77ecd83

Browse files
committed
http2: avoid copying the options in respond()
respond() copied the user-provided options object on every call just so it could normalize and locally flip options.endStream, and prepareResponseHeadersObject() then looked the :status and date fields up again on the dictionary-mode null-prototype headers copy it had just built. Use a local variable for endStream and pick up :status/date while copying the headers instead. No measurable throughput change on its own; this removes an object clone and several dictionary-mode property lookups per response. Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent bfd97c5 commit 77ecd83

1 file changed

Lines changed: 20 additions & 15 deletions

File tree

lib/internal/http2/core.js

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2813,31 +2813,35 @@ function prepareResponseHeaders(stream, headersParam, options) {
28132813
function prepareResponseHeadersObject(oldHeaders, options) {
28142814
assertIsObject(oldHeaders, 'headers', ['Object', 'Array']);
28152815
const headers = { __proto__: null };
2816+
let statusCode;
2817+
let hasDate = false;
28162818

28172819
if (oldHeaders !== null && oldHeaders !== undefined) {
28182820
// This loop is here for performance reason. Do not change.
2821+
// The :status and date fields are picked up while copying so they do
2822+
// not have to be looked up again on the null-prototype copy.
28192823
for (const key in oldHeaders) {
28202824
if (ObjectHasOwn(oldHeaders, key)) {
2821-
headers[key] = oldHeaders[key];
2825+
const value = oldHeaders[key];
2826+
headers[key] = value;
2827+
if (key === HTTP2_HEADER_STATUS)
2828+
statusCode = value;
2829+
else if (key === HTTP2_HEADER_DATE)
2830+
hasDate = value != null;
28222831
}
28232832
}
28242833
headers[kSensitiveHeaders] = oldHeaders[kSensitiveHeaders];
28252834
}
28262835

2827-
const statusCode =
2828-
headers[HTTP2_HEADER_STATUS] =
2829-
headers[HTTP2_HEADER_STATUS] | 0 || HTTP_STATUS_OK;
2836+
statusCode = headers[HTTP2_HEADER_STATUS] = statusCode | 0 || HTTP_STATUS_OK;
28302837

2831-
if (options.sendDate == null || options.sendDate) {
2832-
headers[HTTP2_HEADER_DATE] ??= utcDate();
2838+
if (!hasDate && (options.sendDate == null || options.sendDate)) {
2839+
headers[HTTP2_HEADER_DATE] = utcDate();
28332840
}
28342841

28352842
validatePreparedResponseHeaders(headers, statusCode);
28362843

2837-
return {
2838-
headers,
2839-
statusCode: headers[HTTP2_HEADER_STATUS],
2840-
};
2844+
return { headers, statusCode };
28412845
}
28422846

28432847
function prepareResponseHeadersArray(headers, options) {
@@ -3222,15 +3226,17 @@ class ServerHttp2Stream extends Http2Stream {
32223226
const state = this[kState];
32233227

32243228
assertIsObject(options, 'options');
3225-
options = { ...options };
3229+
// The options are only read, never mutated, so the user-provided object
3230+
// can be used directly instead of copying it.
3231+
options ??= kEmptyObject;
32263232

32273233
debugStreamObj(this, 'initiating response');
32283234
this[kUpdateTimer]();
32293235

3230-
options.endStream = !!options.endStream;
3236+
const endStream = !!options.endStream;
32313237

32323238
let streamOptions = 0;
3233-
if (options.endStream)
3239+
if (endStream)
32343240
streamOptions |= STREAM_OPTION_EMPTY_PAYLOAD;
32353241

32363242
if (options.waitForTrailers) {
@@ -3253,12 +3259,11 @@ class ServerHttp2Stream extends Http2Stream {
32533259

32543260
// Close the writable side if the endStream option is set or status
32553261
// is one of known codes with no payload, or it's a head request
3256-
if (!!options.endStream ||
3262+
if (endStream ||
32573263
statusCode === HTTP_STATUS_NO_CONTENT ||
32583264
statusCode === HTTP_STATUS_RESET_CONTENT ||
32593265
statusCode === HTTP_STATUS_NOT_MODIFIED ||
32603266
this.headRequest === true) {
3261-
options.endStream = true;
32623267
this.end();
32633268
}
32643269

0 commit comments

Comments
 (0)