http: avoid toLowerCase on the server hot path · nodejs/node@4bd56b3 · GitHub
Skip to content

Commit 4bd56b3

Browse files
authored
http: avoid toLowerCase on the server hot path
After #65802, Host/Expect/body checks still allocate toLowerCase() copies and Expect/HTTP/1.0 TE still read req.headers. Compare names without allocating, read those values from rawHeaders, intern parser header names, and skip Title-Case toLowerCase on common outgoing fields. Refs: #65802 Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com> Assisted-by: a closed-source coding agent PR-URL: #66120 Refs: #65332 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com>
1 parent 823d229 commit 4bd56b3

4 files changed

Lines changed: 165 additions & 30 deletions

File tree

‎lib/_http_incoming.js‎

Lines changed: 44 additions & 6 deletions

‎lib/_http_outgoing.js‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,10 +659,38 @@ function storeHeader(self, state, key, value, validate, lenient) {
659659
matchHeader(self, state, key, value);
660660
}
661661

662+
function lowerOutgoingHeaderName(field) {
663+
switch (field) {
664+
case 'Connection':
665+
case 'connection':
666+
return 'connection';
667+
case 'Content-Length':
668+
case 'content-length':
669+
return 'content-length';
670+
case 'Transfer-Encoding':
671+
case 'transfer-encoding':
672+
return 'transfer-encoding';
673+
case 'Date':
674+
case 'date':
675+
return 'date';
676+
case 'Expect':
677+
case 'expect':
678+
return 'expect';
679+
case 'Trailer':
680+
case 'trailer':
681+
return 'trailer';
682+
case 'Keep-Alive':
683+
case 'keep-alive':
684+
return 'keep-alive';
685+
default:
686+
return field.toLowerCase();
687+
}
688+
}
689+
662690
function matchHeader(self, state, field, value) {
663691
if (field.length < 4 || field.length > 17)
664692
return;
665-
field = field.toLowerCase();
693+
field = lowerOutgoingHeaderName(field);
666694
switch (field) {
667695
case 'connection':
668696
state.connection = true;

‎lib/_http_server.js‎

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ const {
7474
IncomingMessage,
7575
kDetachAbortSignal,
7676
kHeadersCount,
77+
asciiEqualIgnoreCase,
78+
getRawHeader,
7779
} = require('_http_incoming');
7880
const {
7981
ConnResetException,
@@ -225,7 +227,8 @@ function ServerResponse(req, options) {
225227
this._expect_continue = false;
226228

227229
if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) {
228-
this.useChunkedEncodingByDefault = chunkExpression.test(req.headers.te);
230+
this.useChunkedEncodingByDefault =
231+
chunkExpression.test(getRawHeader(req, 'te', true));
229232
this.shouldKeepAlive = false;
230233
}
231234

@@ -1289,10 +1292,8 @@ function hasRequestHeader(req, length, lowerName) {
12891292
const count = req[kHeadersCount];
12901293
for (let i = 0; i < count; i += 2) {
12911294
const key = rawHeaders[i];
1292-
if (key.length === length &&
1293-
(key === lowerName || key.toLowerCase() === lowerName)) {
1295+
if (key.length === length && asciiEqualIgnoreCase(key, lowerName))
12941296
return true;
1295-
}
12961297
}
12971298
return false;
12981299
}
@@ -1301,12 +1302,14 @@ function hasBodyHeaders(req) {
13011302
const rawHeaders = req.rawHeaders;
13021303
const count = req[kHeadersCount];
13031304
for (let i = 0; i < count; i += 2) {
1304-
const length = rawHeaders[i].length;
1305-
if (length === 14 || length === 17) {
1306-
const key = rawHeaders[i].toLowerCase();
1307-
if (key === 'content-length' || key === 'transfer-encoding') {
1305+
const key = rawHeaders[i];
1306+
const length = key.length;
1307+
if (length === 14) {
1308+
if (asciiEqualIgnoreCase(key, 'content-length'))
1309+
return true;
1310+
} else if (length === 17) {
1311+
if (asciiEqualIgnoreCase(key, 'transfer-encoding'))
13081312
return true;
1309-
}
13101313
}
13111314
}
13121315
return false;
@@ -1441,22 +1444,25 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
14411444
server.emit('dropRequest', req, socket);
14421445
res.writeHead(503);
14431446
res.end();
1444-
} else if (hasRequestHeader(req, 6, 'expect')) {
1445-
handled = true;
1446-
1447-
if (continueExpression.test(req.headers.expect)) {
1448-
res._expect_continue = true;
1449-
if (server.listenerCount('checkContinue') > 0) {
1450-
server.emit('checkContinue', req, res);
1447+
} else {
1448+
const expect = getRawHeader(req, 'expect', true);
1449+
if (expect !== undefined) {
1450+
handled = true;
1451+
1452+
if (continueExpression.test(expect)) {
1453+
res._expect_continue = true;
1454+
if (server.listenerCount('checkContinue') > 0) {
1455+
server.emit('checkContinue', req, res);
1456+
} else {
1457+
res.writeContinue();
1458+
server.emit('request', req, res);
1459+
}
1460+
} else if (server.listenerCount('checkExpectation') > 0) {
1461+
server.emit('checkExpectation', req, res);
14511462
} else {
1452-
res.writeContinue();
1453-
server.emit('request', req, res);
1463+
res.writeHead(417);
1464+
res.end();
14541465
}
1455-
} else if (server.listenerCount('checkExpectation') > 0) {
1456-
server.emit('checkExpectation', req, res);
1457-
} else {
1458-
res.writeHead(417);
1459-
res.end();
14601466
}
14611467
}
14621468
}
Lines changed: 63 additions & 0 deletions

0 commit comments

Comments
 (0)