@@ -38,7 +38,7 @@ const { getDefaultHighWaterMark } = require('internal/streams/state');
3838const assert = require ( 'internal/assert' ) ;
3939const EE = require ( 'events' ) ;
4040const Stream = require ( 'stream' ) ;
41- const { kOutHeaders, utcDate , kNeedDrain } = require ( 'internal/http' ) ;
41+ const { kOutHeaders, utcDateHeader , kNeedDrain } = require ( 'internal/http' ) ;
4242const { Buffer } = require ( 'buffer' ) ;
4343const {
4444 _checkIsHttpToken : checkIsHttpToken ,
@@ -89,6 +89,11 @@ const kChunkedLength = Symbol('kChunkedLength');
8989const kUniqueHeaders = Symbol ( 'kUniqueHeaders' ) ;
9090const kBytesWritten = Symbol ( 'kBytesWritten' ) ;
9191const kErrored = Symbol ( 'errored' ) ;
92+ const kLenientCache = Symbol ( 'kLenientCache' ) ;
93+
94+ let keepAliveTimeoutCache = - 1 ;
95+ let keepAliveMaxCache = - 1 ;
96+ let keepAliveHeaderCache = '' ;
9297const kWritableFinished = Symbol ( 'kWritableFinished' ) ;
9398const kEndCallbacks = Symbol ( 'kEndCallbacks' ) ;
9499const kFlushError = Symbol ( 'kFlushError' ) ;
@@ -169,6 +174,7 @@ function OutgoingMessage(options) {
169174 this [ kFlushError ] = null ;
170175 this [ kHighWaterMark ] = options ?. highWaterMark ?? getDefaultHighWaterMark ( ) ;
171176 this [ kRejectNonStandardBodyWrites ] = options ?. rejectNonStandardBodyWrites ?? false ;
177+ this [ kLenientCache ] = null ;
172178}
173179ObjectSetPrototypeOf ( OutgoingMessage . prototype , Stream . prototype ) ;
174180ObjectSetPrototypeOf ( OutgoingMessage , Stream ) ;
@@ -178,27 +184,34 @@ ObjectSetPrototypeOf(OutgoingMessage, Stream);
178184// For ServerResponse: checks the server's httpValidation or insecureHTTPParser
179185// Falls back to global --insecure-http-parser flag.
180186OutgoingMessage . prototype . _isLenientHeaderValidation = function ( ) {
187+ // Options cannot change for the lifetime of a message after headers
188+ // are stored: compute the lookup chain only once.
189+ this [ kLenientCache ] ??= isLenientHeaderValidation ( this ) ;
190+ return this [ kLenientCache ] ;
191+ } ;
192+
193+ function isLenientHeaderValidation ( msg ) {
181194 // New httpValidation option takes priority (ClientRequest case)
182- if ( this . httpValidation !== undefined ) {
183- return this . httpValidation !== 'strict' ;
195+ if ( msg . httpValidation !== undefined ) {
196+ return msg . httpValidation !== 'strict' ;
184197 }
185198 // ServerResponse: check server's httpValidation option
186- const serverHttpValidation = this . req ?. socket ?. server ?. httpValidation ;
199+ const serverHttpValidation = msg . req ?. socket ?. server ?. httpValidation ;
187200 if ( serverHttpValidation !== undefined ) {
188201 return serverHttpValidation !== 'strict' ;
189202 }
190203 // Legacy insecureHTTPParser - ClientRequest has it directly
191- if ( typeof this . insecureHTTPParser === 'boolean' ) {
192- return this . insecureHTTPParser ;
204+ if ( typeof msg . insecureHTTPParser === 'boolean' ) {
205+ return msg . insecureHTTPParser ;
193206 }
194207 // ServerResponse can access via req.socket.server
195- const serverOption = this . req ?. socket ?. server ?. insecureHTTPParser ;
208+ const serverOption = msg . req ?. socket ?. server ?. insecureHTTPParser ;
196209 if ( typeof serverOption === 'boolean' ) {
197210 return serverOption ;
198211 }
199212 // Fall back to global option
200213 return isLenient ( ) ;
201- } ;
214+ }
202215
203216ObjectDefineProperty ( OutgoingMessage . prototype , 'errored' , {
204217 __proto__ : null ,
@@ -472,7 +485,7 @@ function _storeHeader(firstLine, headers) {
472485 trailer : false ,
473486 header : firstLine ,
474487 } ;
475- const lenient = this . _isLenientHeaderValidation ( ) ;
488+ const lenient = headers ? this . _isLenientHeaderValidation ( ) : false ;
476489
477490 if ( headers ) {
478491 if ( headers === this [ kOutHeaders ] ) {
@@ -508,7 +521,7 @@ function _storeHeader(firstLine, headers) {
508521
509522 // Date header
510523 if ( this . sendDate && ! state . date ) {
511- header += 'Date: ' + utcDate ( ) + '\r\n' ;
524+ header += utcDateHeader ( ) ;
512525 }
513526
514527 // Force the connection to close when the response is a 204 No Content or
@@ -541,14 +554,21 @@ function _storeHeader(firstLine, headers) {
541554 if ( shouldSendKeepAlive && this . maxRequestsOnConnectionReached ) {
542555 header += 'Connection: close\r\n' ;
543556 } else if ( shouldSendKeepAlive ) {
544- header += 'Connection: keep-alive\r\n' ;
545557 if ( this . _keepAliveTimeout && this . _defaultKeepAlive ) {
546- const timeoutSeconds = MathFloor ( this . _keepAliveTimeout / 1000 ) ;
547- let max = '' ;
548- if ( ~ ~ this . _maxRequestsPerSocket > 0 ) {
549- max = `, max=${ this . _maxRequestsPerSocket } ` ;
558+ // Identical for every response of a given server: cache the last
559+ // rendered Connection + Keep-Alive pair.
560+ const timeout = this . _keepAliveTimeout ;
561+ const max = ~ ~ this . _maxRequestsPerSocket ;
562+ if ( timeout !== keepAliveTimeoutCache || max !== keepAliveMaxCache ) {
563+ keepAliveTimeoutCache = timeout ;
564+ keepAliveMaxCache = max ;
565+ keepAliveHeaderCache = 'Connection: keep-alive\r\n' +
566+ `Keep-Alive: timeout=${ MathFloor ( timeout / 1000 ) } ` +
567+ ( max > 0 ? `, max=${ max } ` : '' ) + '\r\n' ;
550568 }
551- header += `Keep-Alive: timeout=${ timeoutSeconds } ${ max } \r\n` ;
569+ header += keepAliveHeaderCache ;
570+ } else {
571+ header += 'Connection: keep-alive\r\n' ;
552572 }
553573 } else {
554574 this . _last = true ;
@@ -639,10 +659,38 @@ function storeHeader(self, state, key, value, validate, lenient) {
639659 matchHeader ( self , state , key , value ) ;
640660}
641661
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+
642690function matchHeader ( self , state , field , value ) {
643691 if ( field . length < 4 || field . length > 17 )
644692 return ;
645- field = field . toLowerCase ( ) ;
693+ field = lowerOutgoingHeaderName ( field ) ;
646694 switch ( field ) {
647695 case 'connection' :
648696 state . connection = true ;
0 commit comments