From 6cfd106a9eb0728b8bc8bd90eb19f87bdca53167 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Sun, 3 Dec 2023 16:02:23 +0900 Subject: [PATCH 01/10] perf: optimize `parseHeaders` --- lib/core/constants.js | 130 ++++++++++++++++++++++++++++++++++++++++++ lib/core/util.js | 12 ++-- test/util.js | 6 ++ 3 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 lib/core/constants.js diff --git a/lib/core/constants.js b/lib/core/constants.js new file mode 100644 index 00000000000..3675d446cce --- /dev/null +++ b/lib/core/constants.js @@ -0,0 +1,130 @@ +/** @type {Record} */ +const headerNameLowerCasedRecord = {} + +// https://developer.mozilla.org/docs/Web/HTTP/Headers +const wellknownHeaderNames = [ + 'Accept', + 'Accept-Encoding', + 'Accept-Language', + 'Accept-Ranges', + 'Access-Control-Allow-Credentials', + 'Access-Control-Allow-Headers', + 'Access-Control-Allow-Methods', + 'Access-Control-Allow-Origin', + 'Access-Control-Expose-Headers', + 'Access-Control-Max-Age', + 'Access-Control-Request-Headers', + 'Access-Control-Request-Method', + 'Age', + 'Allow', + 'Alt-Svc', + 'Alt-Used', + 'Authorization', + 'Cache-Control', + 'Clear-Site-Data', + 'Connection', + 'Content-Disposition', + 'Content-Encoding', + 'Content-Language', + 'Content-Length', + 'Content-Location', + 'Content-Range', + 'Content-Security-Policy', + 'Content-Security-Policy-Report-Only', + 'Content-Type', + 'Cookie', + 'Cross-Origin-Embedder-Policy', + 'Cross-Origin-Opener-Policy', + 'Cross-Origin-Resource-Policy', + 'Date', + 'Device-Memory', + 'Downlink', + 'ECT', + 'ETag', + 'Expect', + 'Expect-CT', + 'Expires', + 'Forwarded', + 'From', + 'Host', + 'If-Match', + 'If-Modified-Since', + 'If-None-Match', + 'If-Range', + 'If-Unmodified-Since', + 'Keep-Alive', + 'Last-Event-ID', + 'Last-Modified', + 'Link', + 'Location', + 'Max-Forwards', + 'Origin', + 'Permissions-Policy', + 'Ping-From', + 'Ping-To', + 'Pragma', + 'Proxy-Authenticate', + 'Proxy-Authorization', + 'RTT', + 'Range', + 'Referer', + 'Referrer-Policy', + 'Refresh', + 'Report-To', + 'Retry-After', + 'Sec-Fetch-Dest', + 'Sec-Fetch-Mode', + 'Sec-Fetch-Site', + 'Sec-Fetch-User', + 'Sec-GPC', + 'Sec-WebSocket-Accept', + 'Sec-WebSocket-Extensions', + 'Sec-WebSocket-Key', + 'Sec-WebSocket-Protocol', + 'Sec-WebSocket-Version', + 'Server', + 'Server-Timing', + 'Service-Worker-Allowed', + 'Service-Worker-Navigation-Preload', + 'Set-Cookie', + 'SourceMap', + 'Strict-Transport-Security', + 'Supports-Loading-Mode', + 'TE', + 'Timing-Allow-Origin', + 'Trailer', + 'Transfer-Encoding', + 'Upgrade', + 'Upgrade-Insecure-Requests', + 'User-Agent', + 'Vary', + 'Via', + 'WWW-Authenticate', + 'X-Content-Type-Options', + 'X-DNS-Prefetch-Control', + 'X-Forwarded-For', + 'X-Forwarded-Host', + 'X-Forwarded-Proto', + 'X-Frame-Options', + 'X-Permitted-Cross-Domain-Policies', + 'X-Pingback', + 'X-Powered-By', + 'X-Requested-With', + 'X-Robots-Tag', + 'X-XSS-Protection' +] + +for (let i = 0; i < wellknownHeaderNames.length; ++i) { + const key = wellknownHeaderNames[wellknownHeaderNames[i]] + const lowerCasedKey = key.toLowerCase() + headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] = + key +} + +// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. +Object.setPrototypeOf(headerNameLowerCasedRecord, null) + +module.exports = { + wellknownHeaderNames, + headerNameLowerCasedRecord +} diff --git a/lib/core/util.js b/lib/core/util.js index 82cf3260b9f..35a935a5e71 100644 --- a/lib/core/util.js +++ b/lib/core/util.js @@ -9,6 +9,7 @@ const { InvalidArgumentError } = require('./errors') const { Blob } = require('buffer') const nodeUtil = require('util') const { stringify } = require('querystring') +const { headerNameLowerCasedRecord } = require('./constants') const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v)) @@ -223,19 +224,20 @@ function parseHeaders (headers, obj = {}) { if (!Array.isArray(headers)) return headers for (let i = 0; i < headers.length; i += 2) { - const key = headers[i].toString().toLowerCase() - let val = obj[key] + const key = headers[i].toString() + const lowerCasedKey = headerNameLowerCasedRecord[key] ?? key.toLowerCase() + let val = obj[lowerCasedKey] if (!val) { if (Array.isArray(headers[i + 1])) { - obj[key] = headers[i + 1].map(x => x.toString('utf8')) + obj[lowerCasedKey] = headers[i + 1].map(x => x.toString('utf8')) } else { - obj[key] = headers[i + 1].toString('utf8') + obj[lowerCasedKey] = headers[i + 1].toString('utf8') } } else { if (!Array.isArray(val)) { val = [val] - obj[key] = val + obj[lowerCasedKey] = val } val.push(headers[i + 1].toString('utf8')) } diff --git a/test/util.js b/test/util.js index cd5c4ed3044..75a4d8c1617 100644 --- a/test/util.js +++ b/test/util.js @@ -6,6 +6,7 @@ const { Stream } = require('stream') const { EventEmitter } = require('events') const util = require('../lib/core/util') +const { headerNameLowerCasedRecord } = require('../lib/core/constants') const { InvalidArgumentError } = require('../lib/core/errors') test('isStream', (t) => { @@ -121,3 +122,8 @@ test('buildURL', (t) => { t.end() }) + +test('headerNameLowerCasedRecord', (t) => { + t.plan(1) + t.ok(typeof headerNameLowerCasedRecord.hasOwnProperty === 'undefined') +}) From 8415ca486962bd950877b292d7d313062657df52 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Sun, 3 Dec 2023 16:07:20 +0900 Subject: [PATCH 02/10] fixup --- lib/core/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/constants.js b/lib/core/constants.js index 3675d446cce..dcb607a6a1f 100644 --- a/lib/core/constants.js +++ b/lib/core/constants.js @@ -118,7 +118,7 @@ for (let i = 0; i < wellknownHeaderNames.length; ++i) { const key = wellknownHeaderNames[wellknownHeaderNames[i]] const lowerCasedKey = key.toLowerCase() headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] = - key + lowerCasedKey } // Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. From 14a8ff153f787c94f5b449ec6b5f1980102a79e8 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Sun, 3 Dec 2023 16:08:37 +0900 Subject: [PATCH 03/10] fixup --- lib/core/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/util.js b/lib/core/util.js index 35a935a5e71..886b72da520 100644 --- a/lib/core/util.js +++ b/lib/core/util.js @@ -226,7 +226,7 @@ function parseHeaders (headers, obj = {}) { for (let i = 0; i < headers.length; i += 2) { const key = headers[i].toString() const lowerCasedKey = headerNameLowerCasedRecord[key] ?? key.toLowerCase() - let val = obj[lowerCasedKey] + let val = obj[key] if (!val) { if (Array.isArray(headers[i + 1])) { From 6cafab51fab06b6bf9088358fc5504b1b0da5275 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Sun, 3 Dec 2023 16:11:54 +0900 Subject: [PATCH 04/10] Revert "fixup" This reverts commit 8415ca486962bd950877b292d7d313062657df52. --- lib/core/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/constants.js b/lib/core/constants.js index dcb607a6a1f..3675d446cce 100644 --- a/lib/core/constants.js +++ b/lib/core/constants.js @@ -118,7 +118,7 @@ for (let i = 0; i < wellknownHeaderNames.length; ++i) { const key = wellknownHeaderNames[wellknownHeaderNames[i]] const lowerCasedKey = key.toLowerCase() headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] = - lowerCasedKey + key } // Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. From 04b5336de11f6344e600ed4eb0e20e2550a37255 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Sun, 3 Dec 2023 16:12:50 +0900 Subject: [PATCH 05/10] fixup --- lib/core/constants.js | 2 +- lib/core/util.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/constants.js b/lib/core/constants.js index 3675d446cce..dcb607a6a1f 100644 --- a/lib/core/constants.js +++ b/lib/core/constants.js @@ -118,7 +118,7 @@ for (let i = 0; i < wellknownHeaderNames.length; ++i) { const key = wellknownHeaderNames[wellknownHeaderNames[i]] const lowerCasedKey = key.toLowerCase() headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] = - key + lowerCasedKey } // Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. diff --git a/lib/core/util.js b/lib/core/util.js index 886b72da520..35a935a5e71 100644 --- a/lib/core/util.js +++ b/lib/core/util.js @@ -226,7 +226,7 @@ function parseHeaders (headers, obj = {}) { for (let i = 0; i < headers.length; i += 2) { const key = headers[i].toString() const lowerCasedKey = headerNameLowerCasedRecord[key] ?? key.toLowerCase() - let val = obj[key] + let val = obj[lowerCasedKey] if (!val) { if (Array.isArray(headers[i + 1])) { From 7b5f996be2e72a1e072765967c73429f67a63e21 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Sun, 3 Dec 2023 16:17:07 +0900 Subject: [PATCH 06/10] fixup --- lib/core/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/constants.js b/lib/core/constants.js index dcb607a6a1f..de7fd2dd1df 100644 --- a/lib/core/constants.js +++ b/lib/core/constants.js @@ -115,7 +115,7 @@ const wellknownHeaderNames = [ ] for (let i = 0; i < wellknownHeaderNames.length; ++i) { - const key = wellknownHeaderNames[wellknownHeaderNames[i]] + const key = wellknownHeaderNames[i] const lowerCasedKey = key.toLowerCase() headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] = lowerCasedKey From e6e5884b28e2ab64fb502125c49feda79cf43af8 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Sun, 3 Dec 2023 16:21:41 +0900 Subject: [PATCH 07/10] reduce --- lib/core/constants.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/core/constants.js b/lib/core/constants.js index de7fd2dd1df..200c98ea67e 100644 --- a/lib/core/constants.js +++ b/lib/core/constants.js @@ -76,7 +76,6 @@ const wellknownHeaderNames = [ 'Sec-Fetch-Mode', 'Sec-Fetch-Site', 'Sec-Fetch-User', - 'Sec-GPC', 'Sec-WebSocket-Accept', 'Sec-WebSocket-Extensions', 'Sec-WebSocket-Key', @@ -102,15 +101,10 @@ const wellknownHeaderNames = [ 'WWW-Authenticate', 'X-Content-Type-Options', 'X-DNS-Prefetch-Control', - 'X-Forwarded-For', - 'X-Forwarded-Host', - 'X-Forwarded-Proto', 'X-Frame-Options', 'X-Permitted-Cross-Domain-Policies', - 'X-Pingback', 'X-Powered-By', 'X-Requested-With', - 'X-Robots-Tag', 'X-XSS-Protection' ] From 05c9301b368449a65f18c777527b3fbb38dcda18 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Sun, 3 Dec 2023 16:27:47 +0900 Subject: [PATCH 08/10] reduce --- lib/core/constants.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/core/constants.js b/lib/core/constants.js index 200c98ea67e..0f827cc4ae0 100644 --- a/lib/core/constants.js +++ b/lib/core/constants.js @@ -53,15 +53,12 @@ const wellknownHeaderNames = [ 'If-Range', 'If-Unmodified-Since', 'Keep-Alive', - 'Last-Event-ID', 'Last-Modified', 'Link', 'Location', 'Max-Forwards', 'Origin', 'Permissions-Policy', - 'Ping-From', - 'Ping-To', 'Pragma', 'Proxy-Authenticate', 'Proxy-Authorization', @@ -70,12 +67,7 @@ const wellknownHeaderNames = [ 'Referer', 'Referrer-Policy', 'Refresh', - 'Report-To', 'Retry-After', - 'Sec-Fetch-Dest', - 'Sec-Fetch-Mode', - 'Sec-Fetch-Site', - 'Sec-Fetch-User', 'Sec-WebSocket-Accept', 'Sec-WebSocket-Extensions', 'Sec-WebSocket-Key', From fb804d513e4339f316bc086da642c8f3dd97a51c Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Sun, 3 Dec 2023 17:59:58 +0900 Subject: [PATCH 09/10] perf: avoid Array.isArray --- lib/core/util.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/core/util.js b/lib/core/util.js index 35a935a5e71..8e0806a7627 100644 --- a/lib/core/util.js +++ b/lib/core/util.js @@ -229,10 +229,13 @@ function parseHeaders (headers, obj = {}) { let val = obj[lowerCasedKey] if (!val) { - if (Array.isArray(headers[i + 1])) { - obj[lowerCasedKey] = headers[i + 1].map(x => x.toString('utf8')) + const headersValue = headers[i + 1] + if (typeof headersValue === 'string') { + obj[lowerCasedKey] = headersValue + } else if (Array.isArray(headersValue)) { + obj[lowerCasedKey] = headersValue.map(x => x.toString('utf8')) } else { - obj[lowerCasedKey] = headers[i + 1].toString('utf8') + obj[lowerCasedKey] = headersValue.toString('utf8') } } else { if (!Array.isArray(val)) { From df412f59238a302749e50aab0d3525ff5c99c838 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Sun, 3 Dec 2023 18:02:39 +0900 Subject: [PATCH 10/10] format --- lib/core/util.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/core/util.js b/lib/core/util.js index 8e0806a7627..5a28529b663 100644 --- a/lib/core/util.js +++ b/lib/core/util.js @@ -232,10 +232,8 @@ function parseHeaders (headers, obj = {}) { const headersValue = headers[i + 1] if (typeof headersValue === 'string') { obj[lowerCasedKey] = headersValue - } else if (Array.isArray(headersValue)) { - obj[lowerCasedKey] = headersValue.map(x => x.toString('utf8')) } else { - obj[lowerCasedKey] = headersValue.toString('utf8') + obj[lowerCasedKey] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('utf8')) : headersValue.toString('utf8') } } else { if (!Array.isArray(val)) {