@@ -67,6 +67,7 @@ const {
6767 ERR_STREAM_DESTROYED ,
6868 ERR_STREAM_NULL_VALUES ,
6969 ERR_STREAM_WRITE_AFTER_END ,
70+ ERR_UNKNOWN_ENCODING ,
7071 } ,
7172 hideStackFrames,
7273} = require ( 'internal/errors' ) ;
@@ -82,6 +83,7 @@ let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
8283} ) ;
8384
8485const kCorked = Symbol ( 'corked' ) ;
86+ const kAutoCorked = Symbol ( 'autoCorked' ) ;
8587const kSocket = Symbol ( 'kSocket' ) ;
8688const kChunkedBuffer = Symbol ( 'kChunkedBuffer' ) ;
8789const kChunkedLength = Symbol ( 'kChunkedLength' ) ;
@@ -147,6 +149,7 @@ function OutgoingMessage(options) {
147149 this . finished = false ;
148150 this . _headerSent = false ;
149151 this [ kCorked ] = 0 ;
152+ this [ kAutoCorked ] = false ;
150153 this [ kChunkedBuffer ] = [ ] ;
151154 this [ kChunkedLength ] = 0 ;
152155 this . _closed = false ;
@@ -225,10 +228,19 @@ ObjectDefineProperty(OutgoingMessage.prototype, 'writableObjectMode', {
225228 } ,
226229} ) ;
227230
231+ function chunkedBufferLength ( msg ) {
232+ const len = msg [ kChunkedLength ] ;
233+ if ( len === 0 ) {
234+ return 0 ;
235+ }
236+
237+ return len + len . toString ( 16 ) . length + 4 + ( ! msg . _headerSent && msg . _header !== null ? msg . _header . length : 0 ) ;
238+ }
239+
228240ObjectDefineProperty ( OutgoingMessage . prototype , 'writableLength' , {
229241 __proto__ : null ,
230242 get ( ) {
231- return this . outputSize + this [ kChunkedLength ] + ( this [ kSocket ] ? this [ kSocket ] . writableLength : 0 ) ;
243+ return this . outputSize + chunkedBufferLength ( this ) + ( this [ kSocket ] ? this [ kSocket ] . writableLength : 0 ) ;
232244 } ,
233245} ) ;
234246
@@ -297,44 +309,85 @@ OutgoingMessage.prototype.cork = function cork() {
297309 }
298310} ;
299311
300- OutgoingMessage . prototype . uncork = function uncork ( ) {
301- this [ kCorked ] -- ;
302- if ( this [ kSocket ] ) {
303- this [ kSocket ] . uncork ( ) ;
312+ function callChunkedCallbacks ( callbacks , error ) {
313+ for ( let n = 0 ; n < callbacks . length ; n ++ ) {
314+ callbacks [ n ] ( error ) ;
304315 }
316+ }
305317
306- if ( this [ kCorked ] || this [ kChunkedBuffer ] . length === 0 ) {
318+ function destroyChunkedBuffer ( msg , error ) {
319+ const buf = msg [ kChunkedBuffer ] ;
320+ if ( buf . length === 0 ) {
307321 return ;
308322 }
309323
310- const len = this [ kChunkedLength ] ;
311- const buf = this [ kChunkedBuffer ] ;
324+ const callbacks = [ ] ;
325+ for ( let n = 2 ; n < buf . length ; n += 3 ) {
326+ if ( buf [ n ] !== nop ) {
327+ callbacks . push ( buf [ n ] ) ;
328+ }
329+ }
330+
331+ buf . length = 0 ;
332+ msg [ kChunkedLength ] = 0 ;
333+ if ( callbacks . length !== 0 ) {
334+ process . nextTick ( callChunkedCallbacks , callbacks , error || new ERR_STREAM_DESTROYED ( 'write' ) ) ;
335+ }
336+ }
337+
338+ function flushChunkedBuffer ( msg ) {
339+ if ( msg . destroyed || msg [ kSocket ] ?. destroyed ) {
340+ destroyChunkedBuffer ( msg , msg [ kErrored ] || msg [ kSocket ] ?. _writableState ?. errored ) ;
341+ return false ;
342+ }
312343
313- assert ( this . chunkedEncoding ) ;
344+ const buf = msg [ kChunkedBuffer ] ;
345+ const len = msg [ kChunkedLength ] ;
314346
315- let callbacks ;
316- this . _send ( len . toString ( 16 ) , 'latin1' , null ) ;
317- this . _send ( crlf_buf , null , null ) ;
347+ assert ( msg . chunkedEncoding ) ;
348+
349+ const callbacks = [ ] ;
350+ msg . _send ( len . toString ( 16 ) , 'latin1' , null ) ;
351+ msg . _send ( crlf_buf , null , null ) ;
318352 for ( let n = 0 ; n < buf . length ; n += 3 ) {
319- this . _send ( buf [ n + 0 ] , buf [ n + 1 ] , null ) ;
320- if ( buf [ n + 2 ] ) {
321- callbacks ??= [ ] ;
353+ msg . _send ( buf [ n ] , buf [ n + 1 ] , null ) ;
354+ if ( buf [ n + 2 ] !== nop ) {
322355 callbacks . push ( buf [ n + 2 ] ) ;
323356 }
324357 }
325- this . _send ( crlf_buf , null , callbacks . length ? ( err ) => {
326- for ( const callback of callbacks ) {
327- callback ( err ) ;
328- }
329- } : null ) ;
358+ msg . _send ( crlf_buf , null , callbacks . length === 0 ? null : ( error ) => callChunkedCallbacks ( callbacks , error ) ) ;
330359
331- this [ kChunkedBuffer ] . length = 0 ;
332- this [ kChunkedLength ] = 0 ;
360+ buf . length = 0 ;
361+ msg [ kChunkedLength ] = 0 ;
362+ return true ;
363+ }
364+
365+ function emitDrainIfNeeded ( msg ) {
366+ if ( msg [ kNeedDrain ] && msg . writableLength === 0 ) {
367+ msg [ kNeedDrain ] = false ;
368+ msg . emit ( 'drain' ) ;
369+ }
370+ }
333371
334- // If we had a pending drain and flushed all data, emit the drain event.
335- if ( this [ kNeedDrain ] && this . writableLength === 0 ) {
336- this [ kNeedDrain ] = false ;
337- this . emit ( 'drain' ) ;
372+ OutgoingMessage . prototype . uncork = function uncork ( ) {
373+ if ( this [ kCorked ] === 0 ) {
374+ return ;
375+ }
376+ this [ kCorked ] -- ;
377+
378+ const hasBufferedChunks = this [ kCorked ] === 0 && this [ kChunkedBuffer ] . length !== 0 ;
379+ let flushed = false ;
380+ try {
381+ if ( hasBufferedChunks ) {
382+ flushed = flushChunkedBuffer ( this ) ;
383+ }
384+ } finally {
385+ this [ kSocket ] ?. uncork ( ) ;
386+ }
387+
388+ if ( flushed ) {
389+ // If we had a pending drain and flushed all data, emit the drain event.
390+ emitDrainIfNeeded ( this ) ;
338391 }
339392} ;
340393
@@ -1006,21 +1059,39 @@ function write_(msg, chunk, encoding, callback, fromEnd) {
10061059
10071060 if ( ! fromEnd && msg . socket && ! msg . socket . writableCorked ) {
10081061 msg . socket . cork ( ) ;
1009- process . nextTick ( connectionCorkNT , msg . socket ) ;
1062+ msg [ kAutoCorked ] = true ;
1063+ process . nextTick ( connectionCorkNT , msg , msg . socket ) ;
10101064 }
10111065
10121066 let ret ;
1013- if ( msg . chunkedEncoding && chunk . length !== 0 ) {
1014- len ??= typeof chunk === 'string' ? Buffer . byteLength ( chunk , encoding ) : chunk . byteLength ;
1015- if ( msg [ kCorked ] && msg . _headerSent ) {
1067+ if ( msg . chunkedEncoding ) {
1068+ const buf = msg [ kChunkedBuffer ] ;
1069+ const buffering = ( msg [ kAutoCorked ] || msg [ kCorked ] ) && ( chunk . length !== 0 || buf . length !== 0 ) ;
1070+ if ( buffering ) {
1071+ if ( encoding && ( encoding === 'buffer' ? typeof chunk === 'string' : ! Buffer . isEncoding ( encoding ) ) ) {
1072+ throw new ERR_UNKNOWN_ENCODING ( encoding ) ;
1073+ }
1074+
1075+ if ( chunk . length !== 0 ) {
1076+ len ??= typeof chunk === 'string' ? Buffer . byteLength ( chunk , encoding ) : chunk . byteLength ;
1077+ if ( typeof chunk !== 'string' && ! ( chunk instanceof Buffer ) ) {
1078+ chunk = Stream . _uint8ArrayToBuffer ( chunk ) ;
1079+ }
1080+ msg [ kChunkedLength ] += len ;
1081+ }
10161082 msg [ kChunkedBuffer ] . push ( chunk , encoding , callback ) ;
1017- msg [ kChunkedLength ] += len ;
1018- ret = msg [ kChunkedLength ] < msg [ kHighWaterMark ] ;
1019- } else {
1083+ ret = msg . writableLength < msg . writableHighWaterMark ;
1084+ if ( msg [ kAutoCorked ] && msg [ kCorked ] === 0 && chunkedBufferLength ( msg ) >= msg . writableHighWaterMark ) {
1085+ flushChunkedBuffer ( msg ) ;
1086+ }
1087+ } else if ( chunk . length !== 0 ) {
1088+ len ??= typeof chunk === 'string' ? Buffer . byteLength ( chunk , encoding ) : chunk . byteLength ;
10201089 msg . _send ( len . toString ( 16 ) , 'latin1' , null ) ;
10211090 msg . _send ( crlf_buf , null , null ) ;
10221091 msg . _send ( chunk , encoding , null , len ) ;
10231092 ret = msg . _send ( crlf_buf , null , callback ) ;
1093+ } else {
1094+ ret = msg . _send ( chunk , encoding , callback , len ) ;
10241095 }
10251096 } else {
10261097 ret = msg . _send ( chunk , encoding , callback , len ) ;
@@ -1031,8 +1102,26 @@ function write_(msg, chunk, encoding, callback, fromEnd) {
10311102}
10321103
10331104
1034- function connectionCorkNT ( conn ) {
1035- conn . uncork ( ) ;
1105+ function connectionCorkNT ( msg , conn ) {
1106+ if ( ! msg [ kAutoCorked ] ) {
1107+ return ;
1108+ }
1109+
1110+ msg [ kAutoCorked ] = false ;
1111+ let flushed = false ;
1112+ try {
1113+ if ( msg . destroyed || conn . destroyed ) {
1114+ destroyChunkedBuffer ( msg , msg [ kErrored ] || conn . _writableState ?. errored ) ;
1115+ } else if ( msg [ kCorked ] === 0 && msg [ kChunkedBuffer ] . length !== 0 ) {
1116+ flushed = flushChunkedBuffer ( msg ) ;
1117+ }
1118+ } finally {
1119+ conn . uncork ( ) ;
1120+ }
1121+
1122+ if ( flushed ) {
1123+ emitDrainIfNeeded ( msg ) ;
1124+ }
10361125}
10371126
10381127OutgoingMessage . prototype . addTrailers = function addTrailers ( headers ) {
@@ -1138,6 +1227,11 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
11381227 throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH ( this [ kBytesWritten ] , this . _contentLength ) ;
11391228 }
11401229
1230+ // Flush message-level corked data before the terminating chunk. Keep the
1231+ // socket corked so all HTTP framing can be written as a single batch.
1232+ const hasBufferedChunks = this [ kChunkedBuffer ] . length !== 0 ;
1233+ const flushed = hasBufferedChunks && flushChunkedBuffer ( this ) ;
1234+
11411235 const finish = onFinish . bind ( undefined , this ) ;
11421236
11431237 if ( this . _hasBody && this . chunkedEncoding ) {
@@ -1148,6 +1242,7 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
11481242 process . nextTick ( finish ) ;
11491243 }
11501244
1245+ this [ kAutoCorked ] = false ;
11511246 if ( this [ kSocket ] ) {
11521247 // Fully uncork connection on end().
11531248 this [ kSocket ] . _writableState . corked = 1 ;
@@ -1156,8 +1251,13 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
11561251 this [ kCorked ] = 1 ;
11571252 this . uncork ( ) ;
11581253
1254+ // A synchronous drain listener must not write after the terminating chunk.
11591255 this . finished = true ;
11601256
1257+ if ( flushed ) {
1258+ emitDrainIfNeeded ( this ) ;
1259+ }
1260+
11611261 // There is the first message on the outgoing queue, and we've sent
11621262 // everything to the socket.
11631263 debug ( 'outgoing message end.' ) ;
0 commit comments