@@ -71,6 +71,7 @@ const {
7171const {
7272 IncomingMessage,
7373 kDetachAbortSignal,
74+ kHeadersCount,
7475} = require ( '_http_incoming' ) ;
7576const {
7677 ConnResetException,
@@ -113,6 +114,10 @@ const kServerResponse = Symbol('ServerResponse');
113114const kServerResponseStatistics = Symbol ( 'ServerResponseStatistics' ) ;
114115
115116const kOptimizeEmptyRequests = Symbol ( 'OptimizeEmptyRequestsOption' ) ;
117+ const kConnectionState = Symbol ( 'ConnectionState' ) ;
118+ const kResponseOptions = Symbol ( 'ResponseOptions' ) ;
119+
120+ const statusLineCache = [ ] ;
116121
117122const {
118123 hasObserver,
@@ -467,10 +472,18 @@ function writeHead(statusCode, reason, obj) {
467472 headers = obj ;
468473 }
469474
470- if ( checkInvalidHeaderChar ( this . statusMessage ) )
471- throw new ERR_INVALID_CHAR ( 'statusMessage' ) ;
475+ let statusLine ;
476+ if ( this . statusMessage === STATUS_CODES [ statusCode ] ) {
477+ // Default reason phrases contain no invalid characters and are shared
478+ // across responses: cache the whole status line.
479+ statusLine = statusLineCache [ statusCode ] ??=
480+ `HTTP/1.1 ${ statusCode } ${ this . statusMessage } \r\n` ;
481+ } else {
482+ if ( checkInvalidHeaderChar ( this . statusMessage ) )
483+ throw new ERR_INVALID_CHAR ( 'statusMessage' ) ;
472484
473- const statusLine = `HTTP/1.1 ${ statusCode } ${ this . statusMessage } \r\n` ;
485+ statusLine = `HTTP/1.1 ${ statusCode } ${ this . statusMessage } \r\n` ;
486+ }
474487
475488 if ( statusCode === 204 || statusCode === 304 ||
476489 ( statusCode >= 100 && statusCode <= 199 ) ) {
@@ -819,7 +832,11 @@ function connectionListenerInternal(server, socket) {
819832 outgoingData : 0 ,
820833 requestsCount : 0 ,
821834 keepAliveTimeoutSet : false ,
835+ onPendingData : null ,
836+ server,
837+ socket,
822838 } ;
839+ state . onPendingData = updateOutgoingData . bind ( undefined , socket , state ) ;
823840 state . onData = socketOnData . bind ( undefined ,
824841 server , socket , parser , state ) ;
825842 state . onEnd = socketOnEnd . bind ( undefined ,
@@ -1157,8 +1174,61 @@ function emitCloseNT(self) {
11571174 }
11581175}
11591176
1160- function hasBodyHeaders ( headers ) {
1161- return ( 'content-length' in headers ) || ( 'transfer-encoding' in headers ) ;
1177+ // Check for the presence of a request header by scanning rawHeaders instead
1178+ // of materializing the req.headers object. Only the first req[kHeadersCount]
1179+ // entries are considered, matching what the req.headers getter exposes.
1180+ function hasRequestHeader ( req , length , lowerName ) {
1181+ const rawHeaders = req . rawHeaders ;
1182+ const count = req [ kHeadersCount ] ;
1183+ for ( let i = 0 ; i < count ; i += 2 ) {
1184+ const key = rawHeaders [ i ] ;
1185+ if ( key . length === length &&
1186+ ( key === lowerName || key . toLowerCase ( ) === lowerName ) ) {
1187+ return true ;
1188+ }
1189+ }
1190+ return false ;
1191+ }
1192+
1193+ function hasBodyHeaders ( req ) {
1194+ const rawHeaders = req . rawHeaders ;
1195+ const count = req [ kHeadersCount ] ;
1196+ for ( let i = 0 ; i < count ; i += 2 ) {
1197+ const length = rawHeaders [ i ] . length ;
1198+ if ( length === 14 || length === 17 ) {
1199+ const key = rawHeaders [ i ] . toLowerCase ( ) ;
1200+ if ( key === 'content-length' || key === 'transfer-encoding' ) {
1201+ return true ;
1202+ }
1203+ }
1204+ }
1205+ return false ;
1206+ }
1207+
1208+ // The options object passed to the ServerResponse constructor is identical
1209+ // for every request of a given server: cache it. Custom response classes get
1210+ // a fresh object since they may retain or mutate it.
1211+ function getResponseOptions ( server , socket ) {
1212+ const highWaterMark = socket . writableHighWaterMark ;
1213+ const rejectNonStandardBodyWrites = server . rejectNonStandardBodyWrites ;
1214+ if ( server [ kServerResponse ] !== ServerResponse ) {
1215+ return { highWaterMark, rejectNonStandardBodyWrites } ;
1216+ }
1217+ let options = server [ kResponseOptions ] ;
1218+ if ( options === undefined ||
1219+ options . highWaterMark !== highWaterMark ||
1220+ options . rejectNonStandardBodyWrites !== rejectNonStandardBodyWrites ) {
1221+ options = server [ kResponseOptions ] =
1222+ { highWaterMark, rejectNonStandardBodyWrites } ;
1223+ }
1224+ return options ;
1225+ }
1226+
1227+ // Shared 'finish' listener: everything resOnFinish needs is reachable from
1228+ // the response, avoiding a bound function per request.
1229+ function onResponseFinish ( ) {
1230+ const state = this [ kConnectionState ] ;
1231+ resOnFinish ( this . req , this , state . socket , state , state . server ) ;
11621232}
11631233
11641234// The following callback is issued after the headers have been read on a
@@ -1190,15 +1260,11 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
11901260 }
11911261 }
11921262
1193- const res = new server [ kServerResponse ] ( req ,
1194- {
1195- highWaterMark : socket . writableHighWaterMark ,
1196- rejectNonStandardBodyWrites : server . rejectNonStandardBodyWrites ,
1197- } ) ;
1263+ const res = new server [ kServerResponse ] ( req , getResponseOptions ( server , socket ) ) ;
11981264 res . _keepAliveTimeout = server . keepAliveTimeout ;
11991265 res . _maxRequestsPerSocket = server . maxRequestsPerSocket ;
1200- res . _onPendingData = updateOutgoingData . bind ( undefined ,
1201- socket , state ) ;
1266+ res . _onPendingData = state . onPendingData ;
1267+ res [ kConnectionState ] = state ;
12021268
12031269 res . shouldKeepAlive = keepAlive ;
12041270 res [ kUniqueHeaders ] = server [ kUniqueHeaders ] ;
@@ -1213,7 +1279,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
12131279 }
12141280
12151281 // Check if we should optimize empty requests (those without Content-Length or Transfer-Encoding headers)
1216- const shouldOptimize = server [ kOptimizeEmptyRequests ] === true && ! hasBodyHeaders ( req . headers ) ;
1282+ const shouldOptimize = server [ kOptimizeEmptyRequests ] === true && ! hasBodyHeaders ( req ) ;
12171283
12181284 if ( shouldOptimize ) {
12191285 // Fast processing where emitting 'data', 'end' and 'close' events is
@@ -1234,9 +1300,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
12341300
12351301 // When we're finished writing the response, check if this is the last
12361302 // response, if so destroy the socket.
1237- res . on ( 'finish' ,
1238- resOnFinish . bind ( undefined ,
1239- req , res , socket , state , server ) ) ;
1303+ res . on ( 'finish' , onResponseFinish ) ;
12401304
12411305 let handled = false ;
12421306
@@ -1246,7 +1310,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
12461310 // From RFC 7230 5.4 https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
12471311 // A server MUST respond with a 400 (Bad Request) status code to any
12481312 // HTTP/1.1 request message that lacks a Host header field
1249- if ( server . requireHostHeader && req . headers . host === undefined ) {
1313+ if ( server . requireHostHeader && ! hasRequestHeader ( req , 4 , 'host' ) ) {
12501314 res . writeHead ( 400 , [ 'Connection' , 'close' ] ) ;
12511315 res . end ( ) ;
12521316 return 0 ;
@@ -1269,7 +1333,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
12691333 server . emit ( 'dropRequest' , req , socket ) ;
12701334 res . writeHead ( 503 ) ;
12711335 res . end ( ) ;
1272- } else if ( req . headers . expect !== undefined ) {
1336+ } else if ( hasRequestHeader ( req , 6 , 'expect' ) ) {
12731337 handled = true ;
12741338
12751339 if ( continueExpression . test ( req . headers . expect ) ) {
0 commit comments