@@ -73,6 +73,7 @@ const {
7373const {
7474 IncomingMessage,
7575 kDetachAbortSignal,
76+ kHeadersCount,
7677} = require ( '_http_incoming' ) ;
7778const {
7879 ConnResetException,
@@ -116,6 +117,10 @@ const kServerResponseStatistics = Symbol('ServerResponseStatistics');
116117const kUpgradeStream = Symbol ( 'UpgradeStream' ) ;
117118
118119const kOptimizeEmptyRequests = Symbol ( 'OptimizeEmptyRequestsOption' ) ;
120+ const kConnectionState = Symbol ( 'ConnectionState' ) ;
121+ const kResponseOptions = Symbol ( 'ResponseOptions' ) ;
122+
123+ const statusLineCache = [ ] ;
119124
120125const {
121126 hasObserver,
@@ -470,10 +475,18 @@ function writeHead(statusCode, reason, obj) {
470475 headers = obj ;
471476 }
472477
473- if ( checkInvalidHeaderChar ( this . statusMessage ) )
474- throw new ERR_INVALID_CHAR ( 'statusMessage' ) ;
478+ let statusLine ;
479+ if ( this . statusMessage === STATUS_CODES [ statusCode ] ) {
480+ // Default reason phrases contain no invalid characters and are shared
481+ // across responses: cache the whole status line.
482+ statusLine = statusLineCache [ statusCode ] ??=
483+ `HTTP/1.1 ${ statusCode } ${ this . statusMessage } \r\n` ;
484+ } else {
485+ if ( checkInvalidHeaderChar ( this . statusMessage ) )
486+ throw new ERR_INVALID_CHAR ( 'statusMessage' ) ;
475487
476- const statusLine = `HTTP/1.1 ${ statusCode } ${ this . statusMessage } \r\n` ;
488+ statusLine = `HTTP/1.1 ${ statusCode } ${ this . statusMessage } \r\n` ;
489+ }
477490
478491 if ( statusCode === 204 || statusCode === 304 ||
479492 ( statusCode >= 100 && statusCode <= 199 ) ) {
@@ -820,7 +833,11 @@ function connectionListenerInternal(server, socket) {
820833 outgoingData : 0 ,
821834 requestsCount : 0 ,
822835 keepAliveTimeoutSet : false ,
836+ onPendingData : null ,
837+ server,
838+ socket,
823839 } ;
840+ state . onPendingData = updateOutgoingData . bind ( undefined , socket , state ) ;
824841 state . onData = socketOnData . bind ( undefined ,
825842 server , socket , parser , state ) ;
826843 state . onEnd = socketOnEnd . bind ( undefined ,
@@ -1257,8 +1274,61 @@ function emitCloseNT(self) {
12571274 }
12581275}
12591276
1260- function hasBodyHeaders ( headers ) {
1261- return ( 'content-length' in headers ) || ( 'transfer-encoding' in headers ) ;
1277+ // Check for the presence of a request header by scanning rawHeaders instead
1278+ // of materializing the req.headers object. Only the first req[kHeadersCount]
1279+ // entries are considered, matching what the req.headers getter exposes.
1280+ function hasRequestHeader ( req , length , lowerName ) {
1281+ const rawHeaders = req . rawHeaders ;
1282+ const count = req [ kHeadersCount ] ;
1283+ for ( let i = 0 ; i < count ; i += 2 ) {
1284+ const key = rawHeaders [ i ] ;
1285+ if ( key . length === length &&
1286+ ( key === lowerName || key . toLowerCase ( ) === lowerName ) ) {
1287+ return true ;
1288+ }
1289+ }
1290+ return false ;
1291+ }
1292+
1293+ function hasBodyHeaders ( req ) {
1294+ const rawHeaders = req . rawHeaders ;
1295+ const count = req [ kHeadersCount ] ;
1296+ for ( let i = 0 ; i < count ; i += 2 ) {
1297+ const length = rawHeaders [ i ] . length ;
1298+ if ( length === 14 || length === 17 ) {
1299+ const key = rawHeaders [ i ] . toLowerCase ( ) ;
1300+ if ( key === 'content-length' || key === 'transfer-encoding' ) {
1301+ return true ;
1302+ }
1303+ }
1304+ }
1305+ return false ;
1306+ }
1307+
1308+ // The options object passed to the ServerResponse constructor is identical
1309+ // for every request of a given server: cache it. Custom response classes get
1310+ // a fresh object since they may retain or mutate it.
1311+ function getResponseOptions ( server , socket ) {
1312+ const highWaterMark = socket . writableHighWaterMark ;
1313+ const rejectNonStandardBodyWrites = server . rejectNonStandardBodyWrites ;
1314+ if ( server [ kServerResponse ] !== ServerResponse ) {
1315+ return { highWaterMark, rejectNonStandardBodyWrites } ;
1316+ }
1317+ let options = server [ kResponseOptions ] ;
1318+ if ( options === undefined ||
1319+ options . highWaterMark !== highWaterMark ||
1320+ options . rejectNonStandardBodyWrites !== rejectNonStandardBodyWrites ) {
1321+ options = server [ kResponseOptions ] =
1322+ { highWaterMark, rejectNonStandardBodyWrites } ;
1323+ }
1324+ return options ;
1325+ }
1326+
1327+ // Shared 'finish' listener: everything resOnFinish needs is reachable from
1328+ // the response, avoiding a bound function per request.
1329+ function onResponseFinish ( ) {
1330+ const state = this [ kConnectionState ] ;
1331+ resOnFinish ( this . req , this , state . socket , state , state . server ) ;
12621332}
12631333
12641334// The following callback is issued after the headers have been read on a
@@ -1291,15 +1361,11 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
12911361 }
12921362 }
12931363
1294- const res = new server [ kServerResponse ] ( req ,
1295- {
1296- highWaterMark : socket . writableHighWaterMark ,
1297- rejectNonStandardBodyWrites : server . rejectNonStandardBodyWrites ,
1298- } ) ;
1364+ const res = new server [ kServerResponse ] ( req , getResponseOptions ( server , socket ) ) ;
12991365 res . _keepAliveTimeout = server . keepAliveTimeout ;
13001366 res . _maxRequestsPerSocket = server . maxRequestsPerSocket ;
1301- res . _onPendingData = updateOutgoingData . bind ( undefined ,
1302- socket , state ) ;
1367+ res . _onPendingData = state . onPendingData ;
1368+ res [ kConnectionState ] = state ;
13031369
13041370 res . shouldKeepAlive = keepAlive ;
13051371 res [ kUniqueHeaders ] = server [ kUniqueHeaders ] ;
@@ -1314,7 +1380,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
13141380 }
13151381
13161382 // Check if we should optimize empty requests (those without Content-Length or Transfer-Encoding headers)
1317- const shouldOptimize = server [ kOptimizeEmptyRequests ] === true && ! hasBodyHeaders ( req . headers ) ;
1383+ const shouldOptimize = server [ kOptimizeEmptyRequests ] === true && ! hasBodyHeaders ( req ) ;
13181384
13191385 if ( shouldOptimize ) {
13201386 // Fast processing where emitting 'data', 'end' and 'close' events is
@@ -1335,9 +1401,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
13351401
13361402 // When we're finished writing the response, check if this is the last
13371403 // response, if so destroy the socket.
1338- res . on ( 'finish' ,
1339- resOnFinish . bind ( undefined ,
1340- req , res , socket , state , server ) ) ;
1404+ res . on ( 'finish' , onResponseFinish ) ;
13411405
13421406 let handled = false ;
13431407
@@ -1347,7 +1411,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
13471411 // From RFC 7230 5.4 https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
13481412 // A server MUST respond with a 400 (Bad Request) status code to any
13491413 // HTTP/1.1 request message that lacks a Host header field
1350- if ( server . requireHostHeader && req . headers . host === undefined ) {
1414+ if ( server . requireHostHeader && ! hasRequestHeader ( req , 4 , 'host' ) ) {
13511415 res . writeHead ( 400 , [ 'Connection' , 'close' ] ) ;
13521416 res . end ( ) ;
13531417 return 0 ;
@@ -1370,7 +1434,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
13701434 server . emit ( 'dropRequest' , req , socket ) ;
13711435 res . writeHead ( 503 ) ;
13721436 res . end ( ) ;
1373- } else if ( req . headers . expect !== undefined ) {
1437+ } else if ( hasRequestHeader ( req , 6 , 'expect' ) ) {
13741438 handled = true ;
13751439
13761440 if ( continueExpression . test ( req . headers . expect ) ) {
0 commit comments