Skip to content

Commit f3d9f72

Browse files
authored
perf: optimize parseHeaders (#2492)
1 parent e910c6a commit f3d9f72

File tree

3 files changed

+131
-6
lines changed

3 files changed

+131
-6
lines changed

lib/core/constants.js

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/** @type {Record<string, string | undefined>} */
2+
const headerNameLowerCasedRecord = {}
3+
4+
// https://developer.mozilla.org/docs/Web/HTTP/Headers
5+
const wellknownHeaderNames = [
6+
'Accept',
7+
'Accept-Encoding',
8+
'Accept-Language',
9+
'Accept-Ranges',
10+
'Access-Control-Allow-Credentials',
11+
'Access-Control-Allow-Headers',
12+
'Access-Control-Allow-Methods',
13+
'Access-Control-Allow-Origin',
14+
'Access-Control-Expose-Headers',
15+
'Access-Control-Max-Age',
16+
'Access-Control-Request-Headers',
17+
'Access-Control-Request-Method',
18+
'Age',
19+
'Allow',
20+
'Alt-Svc',
21+
'Alt-Used',
22+
'Authorization',
23+
'Cache-Control',
24+
'Clear-Site-Data',
25+
'Connection',
26+
'Content-Disposition',
27+
'Content-Encoding',
28+
'Content-Language',
29+
'Content-Length',
30+
'Content-Location',
31+
'Content-Range',
32+
'Content-Security-Policy',
33+
'Content-Security-Policy-Report-Only',
34+
'Content-Type',
35+
'Cookie',
36+
'Cross-Origin-Embedder-Policy',
37+
'Cross-Origin-Opener-Policy',
38+
'Cross-Origin-Resource-Policy',
39+
'Date',
40+
'Device-Memory',
41+
'Downlink',
42+
'ECT',
43+
'ETag',
44+
'Expect',
45+
'Expect-CT',
46+
'Expires',
47+
'Forwarded',
48+
'From',
49+
'Host',
50+
'If-Match',
51+
'If-Modified-Since',
52+
'If-None-Match',
53+
'If-Range',
54+
'If-Unmodified-Since',
55+
'Keep-Alive',
56+
'Last-Modified',
57+
'Link',
58+
'Location',
59+
'Max-Forwards',
60+
'Origin',
61+
'Permissions-Policy',
62+
'Pragma',
63+
'Proxy-Authenticate',
64+
'Proxy-Authorization',
65+
'RTT',
66+
'Range',
67+
'Referer',
68+
'Referrer-Policy',
69+
'Refresh',
70+
'Retry-After',
71+
'Sec-WebSocket-Accept',
72+
'Sec-WebSocket-Extensions',
73+
'Sec-WebSocket-Key',
74+
'Sec-WebSocket-Protocol',
75+
'Sec-WebSocket-Version',
76+
'Server',
77+
'Server-Timing',
78+
'Service-Worker-Allowed',
79+
'Service-Worker-Navigation-Preload',
80+
'Set-Cookie',
81+
'SourceMap',
82+
'Strict-Transport-Security',
83+
'Supports-Loading-Mode',
84+
'TE',
85+
'Timing-Allow-Origin',
86+
'Trailer',
87+
'Transfer-Encoding',
88+
'Upgrade',
89+
'Upgrade-Insecure-Requests',
90+
'User-Agent',
91+
'Vary',
92+
'Via',
93+
'WWW-Authenticate',
94+
'X-Content-Type-Options',
95+
'X-DNS-Prefetch-Control',
96+
'X-Frame-Options',
97+
'X-Permitted-Cross-Domain-Policies',
98+
'X-Powered-By',
99+
'X-Requested-With',
100+
'X-XSS-Protection'
101+
]
102+
103+
for (let i = 0; i < wellknownHeaderNames.length; ++i) {
104+
const key = wellknownHeaderNames[i]
105+
const lowerCasedKey = key.toLowerCase()
106+
headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] =
107+
lowerCasedKey
108+
}
109+
110+
// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
111+
Object.setPrototypeOf(headerNameLowerCasedRecord, null)
112+
113+
module.exports = {
114+
wellknownHeaderNames,
115+
headerNameLowerCasedRecord
116+
}

lib/core/util.js

+9-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const { InvalidArgumentError } = require('./errors')
99
const { Blob } = require('buffer')
1010
const nodeUtil = require('util')
1111
const { stringify } = require('querystring')
12+
const { headerNameLowerCasedRecord } = require('./constants')
1213

1314
const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v))
1415

@@ -223,19 +224,21 @@ function parseHeaders (headers, obj = {}) {
223224
if (!Array.isArray(headers)) return headers
224225

225226
for (let i = 0; i < headers.length; i += 2) {
226-
const key = headers[i].toString().toLowerCase()
227-
let val = obj[key]
227+
const key = headers[i].toString()
228+
const lowerCasedKey = headerNameLowerCasedRecord[key] ?? key.toLowerCase()
229+
let val = obj[lowerCasedKey]
228230

229231
if (!val) {
230-
if (Array.isArray(headers[i + 1])) {
231-
obj[key] = headers[i + 1].map(x => x.toString('utf8'))
232+
const headersValue = headers[i + 1]
233+
if (typeof headersValue === 'string') {
234+
obj[lowerCasedKey] = headersValue
232235
} else {
233-
obj[key] = headers[i + 1].toString('utf8')
236+
obj[lowerCasedKey] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('utf8')) : headersValue.toString('utf8')
234237
}
235238
} else {
236239
if (!Array.isArray(val)) {
237240
val = [val]
238-
obj[key] = val
241+
obj[lowerCasedKey] = val
239242
}
240243
val.push(headers[i + 1].toString('utf8'))
241244
}

test/util.js

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const { Stream } = require('stream')
66
const { EventEmitter } = require('events')
77

88
const util = require('../lib/core/util')
9+
const { headerNameLowerCasedRecord } = require('../lib/core/constants')
910
const { InvalidArgumentError } = require('../lib/core/errors')
1011

1112
test('isStream', (t) => {
@@ -121,3 +122,8 @@ test('buildURL', (t) => {
121122

122123
t.end()
123124
})
125+
126+
test('headerNameLowerCasedRecord', (t) => {
127+
t.plan(1)
128+
t.ok(typeof headerNameLowerCasedRecord.hasOwnProperty === 'undefined')
129+
})

0 commit comments

Comments
 (0)