Skip to content

Commit 5198297

Browse files
starkwangaddaleax
authored andcommitted
http: improve performance for incoming headers
PR-URL: #26041 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Sakthipriyan Vairamani <thechargingvolcano@gmail.com>
1 parent 9c9aefe commit 5198297

File tree

3 files changed

+124
-118
lines changed

3 files changed

+124
-118
lines changed

benchmark/_http-benchmarkers.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@ class AutocannonBenchmarker {
2525
'-c', options.connections,
2626
'-j',
2727
'-n',
28-
`http://127.0.0.1:${options.port}${options.path}`,
2928
];
29+
for (const field in options.headers) {
30+
args.push('-H', `${field}=${options.headers[field]}`);
31+
}
32+
args.push(`http://127.0.0.1:${options.port}${options.path}`);
3033
const child = child_process.spawn(this.executable, args);
3134
return child;
3235
}

benchmark/http/incoming_headers.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
const common = require('../common.js');
3+
const http = require('http');
4+
5+
const bench = common.createBenchmark(main, {
6+
// unicode confuses ab on os x.
7+
c: [50, 500],
8+
headerDuplicates: [0, 5, 20]
9+
});
10+
11+
function main({ c, headerDuplicates }) {
12+
const server = http.createServer((req, res) => {
13+
res.end();
14+
});
15+
16+
server.listen(common.PORT, () => {
17+
const headers = {
18+
'Content-Type': 'text/plain',
19+
'Accept': 'text/plain',
20+
'User-Agent': 'nodejs-benchmark',
21+
'Date': new Date().toString(),
22+
'Cache-Control': 'no-cache'
23+
};
24+
for (let i = 0; i < headerDuplicates; i++) {
25+
headers[`foo${i}`] = `some header value ${i}`;
26+
}
27+
bench.http({
28+
path: '/',
29+
connections: c,
30+
headers
31+
}, () => {
32+
server.close();
33+
});
34+
});
35+
}

lib/_http_incoming.js

+85-117
Original file line numberDiff line numberDiff line change
@@ -135,133 +135,101 @@ function _addHeaderLines(headers, n) {
135135
// TODO: perhaps http_parser could be returning both raw and lowercased versions
136136
// of known header names to avoid us having to call toLowerCase() for those
137137
// headers.
138-
139-
// 'array' header list is taken from:
140-
// https://mxr.mozilla.org/mozilla/source/netwerk/protocol/http/src/nsHttpHeaderArray.cpp
141-
function matchKnownFields(field) {
142-
var low = false;
143-
while (true) {
144-
switch (field) {
145-
case 'Content-Type':
146-
case 'content-type':
147-
return 'content-type';
148-
case 'Content-Length':
149-
case 'content-length':
150-
return 'content-length';
151-
case 'User-Agent':
152-
case 'user-agent':
153-
return 'user-agent';
154-
case 'Referer':
155-
case 'referer':
156-
return 'referer';
157-
case 'Host':
158-
case 'host':
159-
return 'host';
160-
case 'Authorization':
161-
case 'authorization':
162-
return 'authorization';
163-
case 'Proxy-Authorization':
164-
case 'proxy-authorization':
165-
return 'proxy-authorization';
166-
case 'If-Modified-Since':
167-
case 'if-modified-since':
168-
return 'if-modified-since';
169-
case 'If-Unmodified-Since':
170-
case 'if-unmodified-since':
171-
return 'if-unmodified-since';
172-
case 'From':
173-
case 'from':
174-
return 'from';
175-
case 'Location':
176-
case 'location':
138+
function matchKnownFields(field, lowercased) {
139+
switch (field.length) {
140+
case 3:
141+
if (field === 'Age' || field === 'age') return 'age';
142+
break;
143+
case 4:
144+
if (field === 'Host' || field === 'host') return 'host';
145+
if (field === 'From' || field === 'from') return 'from';
146+
if (field === 'ETag' || field === 'etag') return 'etag';
147+
if (field === 'Date' || field === 'date') return '\u0000date';
148+
if (field === 'Vary' || field === 'vary') return '\u0000vary';
149+
break;
150+
case 6:
151+
if (field === 'Server' || field === 'server') return 'server';
152+
if (field === 'Cookie' || field === 'cookie') return '\u0002cookie';
153+
if (field === 'Origin' || field === 'origin') return '\u0000origin';
154+
if (field === 'Expect' || field === 'expect') return '\u0000expect';
155+
if (field === 'Accept' || field === 'accept') return '\u0000accept';
156+
break;
157+
case 7:
158+
if (field === 'Referer' || field === 'referer') return 'referer';
159+
if (field === 'Expires' || field === 'expires') return 'expires';
160+
if (field === 'Upgrade' || field === 'upgrade') return '\u0000upgrade';
161+
break;
162+
case 8:
163+
if (field === 'Location' || field === 'location')
177164
return 'location';
178-
case 'Max-Forwards':
179-
case 'max-forwards':
180-
return 'max-forwards';
181-
case 'Retry-After':
182-
case 'retry-after':
183-
return 'retry-after';
184-
case 'ETag':
185-
case 'etag':
186-
return 'etag';
187-
case 'Last-Modified':
188-
case 'last-modified':
189-
return 'last-modified';
190-
case 'Server':
191-
case 'server':
192-
return 'server';
193-
case 'Age':
194-
case 'age':
195-
return 'age';
196-
case 'Expires':
197-
case 'expires':
198-
return 'expires';
199-
case 'Set-Cookie':
200-
case 'set-cookie':
165+
if (field === 'If-Match' || field === 'if-match')
166+
return '\u0000if-match';
167+
break;
168+
case 10:
169+
if (field === 'User-Agent' || field === 'user-agent')
170+
return 'user-agent';
171+
if (field === 'Set-Cookie' || field === 'set-cookie')
201172
return '\u0001';
202-
case 'Cookie':
203-
case 'cookie':
204-
return '\u0002cookie';
205-
// The fields below are not used in _addHeaderLine(), but they are common
206-
// headers where we can avoid toLowerCase() if the mixed or lower case
207-
// versions match the first time through.
208-
case 'Transfer-Encoding':
209-
case 'transfer-encoding':
210-
return '\u0000transfer-encoding';
211-
case 'Date':
212-
case 'date':
213-
return '\u0000date';
214-
case 'Connection':
215-
case 'connection':
173+
if (field === 'Connection' || field === 'connection')
216174
return '\u0000connection';
217-
case 'Cache-Control':
218-
case 'cache-control':
175+
break;
176+
case 11:
177+
if (field === 'Retry-After' || field === 'retry-after')
178+
return 'retry-after';
179+
break;
180+
case 12:
181+
if (field === 'Content-Type' || field === 'content-type')
182+
return 'content-type';
183+
if (field === 'Max-Forwards' || field === 'max-forwards')
184+
return 'max-forwards';
185+
break;
186+
case 13:
187+
if (field === 'Authorization' || field === 'authorization')
188+
return 'authorization';
189+
if (field === 'Last-Modified' || field === 'last-modified')
190+
return 'last-modified';
191+
if (field === 'Cache-Control' || field === 'cache-control')
219192
return '\u0000cache-control';
220-
case 'Vary':
221-
case 'vary':
222-
return '\u0000vary';
223-
case 'Content-Encoding':
224-
case 'content-encoding':
225-
return '\u0000content-encoding';
226-
case 'Origin':
227-
case 'origin':
228-
return '\u0000origin';
229-
case 'Upgrade':
230-
case 'upgrade':
231-
return '\u0000upgrade';
232-
case 'Expect':
233-
case 'expect':
234-
return '\u0000expect';
235-
case 'If-Match':
236-
case 'if-match':
237-
return '\u0000if-match';
238-
case 'If-None-Match':
239-
case 'if-none-match':
193+
if (field === 'If-None-Match' || field === 'if-none-match')
240194
return '\u0000if-none-match';
241-
case 'Accept':
242-
case 'accept':
243-
return '\u0000accept';
244-
case 'Accept-Encoding':
245-
case 'accept-encoding':
195+
break;
196+
case 14:
197+
if (field === 'Content-Length' || field === 'content-length')
198+
return 'content-length';
199+
break;
200+
case 15:
201+
if (field === 'Accept-Encoding' || field === 'accept-encoding')
246202
return '\u0000accept-encoding';
247-
case 'Accept-Language':
248-
case 'accept-language':
203+
if (field === 'Accept-Language' || field === 'accept-language')
249204
return '\u0000accept-language';
250-
case 'X-Forwarded-For':
251-
case 'x-forwarded-for':
205+
if (field === 'X-Forwarded-For' || field === 'x-forwarded-for')
252206
return '\u0000x-forwarded-for';
253-
case 'X-Forwarded-Host':
254-
case 'x-forwarded-host':
207+
break;
208+
case 16:
209+
if (field === 'Content-Encoding' || field === 'content-encoding')
210+
return '\u0000content-encoding';
211+
if (field === 'X-Forwarded-Host' || field === 'x-forwarded-host')
255212
return '\u0000x-forwarded-host';
256-
case 'X-Forwarded-Proto':
257-
case 'x-forwarded-proto':
213+
break;
214+
case 17:
215+
if (field === 'If-Modified-Since' || field === 'if-modified-since')
216+
return 'if-modified-since';
217+
if (field === 'Transfer-Encoding' || field === 'transfer-encoding')
218+
return '\u0000transfer-encoding';
219+
if (field === 'X-Forwarded-Proto' || field === 'x-forwarded-proto')
258220
return '\u0000x-forwarded-proto';
259-
default:
260-
if (low)
261-
return '\u0000' + field;
262-
field = field.toLowerCase();
263-
low = true;
264-
}
221+
break;
222+
case 19:
223+
if (field === 'Proxy-Authorization' || field === 'proxy-authorization')
224+
return 'proxy-authorization';
225+
if (field === 'If-Unmodified-Since' || field === 'if-unmodified-since')
226+
return 'if-unmodified-since';
227+
break;
228+
}
229+
if (lowercased) {
230+
return '\u0000' + field;
231+
} else {
232+
return matchKnownFields(field.toLowerCase(), true);
265233
}
266234
}
267235
// Add the given (field, value) pair to the message

0 commit comments

Comments
 (0)