@@ -206,6 +206,7 @@ const STREAM_FLAGS_CLOSED = 0x2;
206
206
const STREAM_FLAGS_HEADERS_SENT = 0x4 ;
207
207
const STREAM_FLAGS_HEAD_REQUEST = 0x8 ;
208
208
const STREAM_FLAGS_ABORTED = 0x10 ;
209
+ const STREAM_FLAGS_HAS_TRAILERS = 0x20 ;
209
210
210
211
const SESSION_FLAGS_PENDING = 0x0 ;
211
212
const SESSION_FLAGS_READY = 0x1 ;
@@ -330,26 +331,13 @@ function onStreamClose(code) {
330
331
if ( stream . destroyed )
331
332
return ;
332
333
333
- const state = stream [ kState ] ;
334
-
335
334
debug ( `Http2Stream ${ stream [ kID ] } [Http2Session ` +
336
335
`${ sessionName ( stream [ kSession ] [ kType ] ) } ]: closed with code ${ code } ` ) ;
337
336
338
- if ( ! stream . closed ) {
339
- // Clear timeout and remove timeout listeners
340
- stream . setTimeout ( 0 ) ;
341
- stream . removeAllListeners ( 'timeout' ) ;
337
+ if ( ! stream . closed )
338
+ closeStream ( stream , code , false ) ;
342
339
343
- // Set the state flags
344
- state . flags |= STREAM_FLAGS_CLOSED ;
345
- state . rstCode = code ;
346
-
347
- // Close the writable side of the stream
348
- abort ( stream ) ;
349
- stream . end ( ) ;
350
- }
351
-
352
- state . fd = - 1 ;
340
+ stream [ kState ] . fd = - 1 ;
353
341
// Defer destroy we actually emit end.
354
342
if ( stream . _readableState . endEmitted || code !== NGHTTP2_NO_ERROR ) {
355
343
// If errored or ended, we can destroy immediately.
@@ -504,7 +492,7 @@ function requestOnConnect(headers, options) {
504
492
505
493
// At this point, the stream should have already been destroyed during
506
494
// the session.destroy() method. Do nothing else.
507
- if ( session . destroyed )
495
+ if ( session === undefined || session . destroyed )
508
496
return ;
509
497
510
498
// If the session was closed while waiting for the connect, destroy
@@ -1412,6 +1400,9 @@ class ClientHttp2Session extends Http2Session {
1412
1400
if ( options . endStream )
1413
1401
stream . end ( ) ;
1414
1402
1403
+ if ( options . waitForTrailers )
1404
+ stream [ kState ] . flags |= STREAM_FLAGS_HAS_TRAILERS ;
1405
+
1415
1406
const onConnect = requestOnConnect . bind ( stream , headersList , options ) ;
1416
1407
if ( this . connecting ) {
1417
1408
this . on ( 'connect' , onConnect ) ;
@@ -1445,32 +1436,70 @@ function afterDoStreamWrite(status, handle) {
1445
1436
}
1446
1437
1447
1438
function streamOnResume ( ) {
1448
- if ( ! this . destroyed && ! this . pending )
1439
+ if ( ! this . destroyed && ! this . pending ) {
1440
+ if ( ! this [ kState ] . didRead )
1441
+ this [ kState ] . didRead = true ;
1449
1442
this [ kHandle ] . readStart ( ) ;
1443
+ }
1450
1444
}
1451
1445
1452
1446
function streamOnPause ( ) {
1453
1447
if ( ! this . destroyed && ! this . pending )
1454
1448
this [ kHandle ] . readStop ( ) ;
1455
1449
}
1456
1450
1457
- // If the writable side of the Http2Stream is still open, emit the
1458
- // 'aborted' event and set the aborted flag.
1459
- function abort ( stream ) {
1460
- if ( ! stream . aborted &&
1461
- ! ( stream . _writableState . ended || stream . _writableState . ending ) ) {
1462
- stream [ kState ] . flags |= STREAM_FLAGS_ABORTED ;
1463
- stream . emit ( 'aborted' ) ;
1464
- }
1465
- }
1466
-
1467
1451
function afterShutdown ( ) {
1468
1452
this . callback ( ) ;
1469
1453
const stream = this . handle [ kOwner ] ;
1470
1454
if ( stream )
1471
1455
stream [ kMaybeDestroy ] ( ) ;
1472
1456
}
1473
1457
1458
+ function closeStream ( stream , code , shouldSubmitRstStream = true ) {
1459
+ const state = stream [ kState ] ;
1460
+ state . flags |= STREAM_FLAGS_CLOSED ;
1461
+ state . rstCode = code ;
1462
+
1463
+ // Clear timeout and remove timeout listeners
1464
+ stream . setTimeout ( 0 ) ;
1465
+ stream . removeAllListeners ( 'timeout' ) ;
1466
+
1467
+ const { ending, finished } = stream . _writableState ;
1468
+
1469
+ if ( ! ending ) {
1470
+ // If the writable side of the Http2Stream is still open, emit the
1471
+ // 'aborted' event and set the aborted flag.
1472
+ if ( ! stream . aborted ) {
1473
+ state . flags |= STREAM_FLAGS_ABORTED ;
1474
+ stream . emit ( 'aborted' ) ;
1475
+ }
1476
+
1477
+ // Close the writable side.
1478
+ stream . end ( ) ;
1479
+ }
1480
+
1481
+ if ( shouldSubmitRstStream ) {
1482
+ const finishFn = finishCloseStream . bind ( stream , code ) ;
1483
+ if ( ! ending || finished || code !== NGHTTP2_NO_ERROR )
1484
+ finishFn ( ) ;
1485
+ else
1486
+ stream . once ( 'finish' , finishFn ) ;
1487
+ }
1488
+ }
1489
+
1490
+ function finishCloseStream ( code ) {
1491
+ const rstStreamFn = submitRstStream . bind ( this , code ) ;
1492
+ // If the handle has not yet been assigned, queue up the request to
1493
+ // ensure that the RST_STREAM frame is sent after the stream ID has
1494
+ // been determined.
1495
+ if ( this . pending ) {
1496
+ this . push ( null ) ;
1497
+ this . once ( 'ready' , rstStreamFn ) ;
1498
+ return ;
1499
+ }
1500
+ rstStreamFn ( ) ;
1501
+ }
1502
+
1474
1503
// An Http2Stream is a Duplex stream that is backed by a
1475
1504
// node::http2::Http2Stream handle implementing StreamBase.
1476
1505
class Http2Stream extends Duplex {
@@ -1490,6 +1519,7 @@ class Http2Stream extends Duplex {
1490
1519
this [ kTimeout ] = null ;
1491
1520
1492
1521
this [ kState ] = {
1522
+ didRead : false ,
1493
1523
flags : STREAM_FLAGS_PENDING ,
1494
1524
rstCode : NGHTTP2_NO_ERROR ,
1495
1525
writeQueueSize : 0 ,
@@ -1756,6 +1786,8 @@ class Http2Stream extends Duplex {
1756
1786
throw headersList ;
1757
1787
this [ kSentTrailers ] = headers ;
1758
1788
1789
+ this [ kState ] . flags &= ~ STREAM_FLAGS_HAS_TRAILERS ;
1790
+
1759
1791
const ret = this [ kHandle ] . trailers ( headersList ) ;
1760
1792
if ( ret < 0 )
1761
1793
this . destroy ( new NghttpError ( ret ) ) ;
@@ -1786,38 +1818,13 @@ class Http2Stream extends Duplex {
1786
1818
if ( callback !== undefined && typeof callback !== 'function' )
1787
1819
throw new ERR_INVALID_CALLBACK ( ) ;
1788
1820
1789
- // Clear timeout and remove timeout listeners
1790
- this . setTimeout ( 0 ) ;
1791
- this . removeAllListeners ( 'timeout' ) ;
1792
-
1793
- // Close the writable
1794
- abort ( this ) ;
1795
- this . end ( ) ;
1796
-
1797
1821
if ( this . closed )
1798
1822
return ;
1799
1823
1800
- const state = this [ kState ] ;
1801
- state . flags |= STREAM_FLAGS_CLOSED ;
1802
- state . rstCode = code ;
1803
-
1804
- if ( callback !== undefined ) {
1824
+ if ( callback !== undefined )
1805
1825
this . once ( 'close' , callback ) ;
1806
- }
1807
-
1808
- if ( this [ kHandle ] === undefined )
1809
- return ;
1810
1826
1811
- const rstStreamFn = submitRstStream . bind ( this , code ) ;
1812
- // If the handle has not yet been assigned, queue up the request to
1813
- // ensure that the RST_STREAM frame is sent after the stream ID has
1814
- // been determined.
1815
- if ( this . pending ) {
1816
- this . push ( null ) ;
1817
- this . once ( 'ready' , rstStreamFn ) ;
1818
- return ;
1819
- }
1820
- rstStreamFn ( ) ;
1827
+ closeStream ( this , code ) ;
1821
1828
}
1822
1829
1823
1830
// Called by this.destroy().
@@ -1832,26 +1839,19 @@ class Http2Stream extends Duplex {
1832
1839
debug ( `Http2Stream ${ this [ kID ] || '<pending>' } [Http2Session ` +
1833
1840
`${ sessionName ( session [ kType ] ) } ]: destroying stream` ) ;
1834
1841
const state = this [ kState ] ;
1835
- const code = state . rstCode =
1836
- err != null ?
1837
- NGHTTP2_INTERNAL_ERROR :
1838
- state . rstCode || NGHTTP2_NO_ERROR ;
1839
- if ( handle !== undefined ) {
1840
- // If the handle exists, we need to close, then destroy the handle
1841
- this . close ( code ) ;
1842
- if ( ! this . _readableState . ended && ! this . _readableState . ending )
1843
- this . push ( null ) ;
1842
+ const code = err != null ?
1843
+ NGHTTP2_INTERNAL_ERROR : ( state . rstCode || NGHTTP2_NO_ERROR ) ;
1844
+
1845
+ const hasHandle = handle !== undefined ;
1846
+
1847
+ if ( ! this . closed )
1848
+ closeStream ( this , code , hasHandle ) ;
1849
+ this . push ( null ) ;
1850
+
1851
+ if ( hasHandle ) {
1844
1852
handle . destroy ( ) ;
1845
1853
session [ kState ] . streams . delete ( id ) ;
1846
1854
} else {
1847
- // Clear timeout and remove timeout listeners
1848
- this . setTimeout ( 0 ) ;
1849
- this . removeAllListeners ( 'timeout' ) ;
1850
-
1851
- state . flags |= STREAM_FLAGS_CLOSED ;
1852
- abort ( this ) ;
1853
- this . end ( ) ;
1854
- this . push ( null ) ;
1855
1855
session [ kState ] . pendingStreams . delete ( this ) ;
1856
1856
}
1857
1857
@@ -1884,13 +1884,23 @@ class Http2Stream extends Duplex {
1884
1884
}
1885
1885
1886
1886
// TODO(mcollina): remove usage of _*State properties
1887
- if ( this . _readableState . ended &&
1888
- this . _writableState . ended &&
1889
- this . _writableState . pendingcb === 0 &&
1890
- this . closed ) {
1891
- this . destroy ( ) ;
1892
- // This should return, but eslint complains.
1893
- // return
1887
+ if ( this . _writableState . ended && this . _writableState . pendingcb === 0 ) {
1888
+ if ( this . _readableState . ended && this . closed ) {
1889
+ this . destroy ( ) ;
1890
+ return ;
1891
+ }
1892
+
1893
+ // We've submitted a response from our server session, have not attempted
1894
+ // to process any incoming data, and have no trailers. This means we can
1895
+ // attempt to gracefully close the session.
1896
+ const state = this [ kState ] ;
1897
+ if ( this . headersSent &&
1898
+ this [ kSession ] [ kType ] === NGHTTP2_SESSION_SERVER &&
1899
+ ! ( state . flags & STREAM_FLAGS_HAS_TRAILERS ) &&
1900
+ ! state . didRead &&
1901
+ ! this . _readableState . resumeScheduled ) {
1902
+ this . close ( ) ;
1903
+ }
1894
1904
}
1895
1905
}
1896
1906
}
@@ -2095,7 +2105,6 @@ function afterOpen(session, options, headers, streamOptions, err, fd) {
2095
2105
}
2096
2106
if ( this . destroyed || this . closed ) {
2097
2107
tryClose ( fd ) ;
2098
- abort ( this ) ;
2099
2108
return ;
2100
2109
}
2101
2110
state . fd = fd ;
@@ -2224,8 +2233,10 @@ class ServerHttp2Stream extends Http2Stream {
2224
2233
if ( options . endStream )
2225
2234
streamOptions |= STREAM_OPTION_EMPTY_PAYLOAD ;
2226
2235
2227
- if ( options . waitForTrailers )
2236
+ if ( options . waitForTrailers ) {
2228
2237
streamOptions |= STREAM_OPTION_GET_TRAILERS ;
2238
+ state . flags |= STREAM_FLAGS_HAS_TRAILERS ;
2239
+ }
2229
2240
2230
2241
headers = processHeaders ( headers ) ;
2231
2242
const statusCode = headers [ HTTP2_HEADER_STATUS ] |= 0 ;
@@ -2285,8 +2296,10 @@ class ServerHttp2Stream extends Http2Stream {
2285
2296
}
2286
2297
2287
2298
let streamOptions = 0 ;
2288
- if ( options . waitForTrailers )
2299
+ if ( options . waitForTrailers ) {
2289
2300
streamOptions |= STREAM_OPTION_GET_TRAILERS ;
2301
+ this [ kState ] . flags |= STREAM_FLAGS_HAS_TRAILERS ;
2302
+ }
2290
2303
2291
2304
if ( typeof fd !== 'number' )
2292
2305
throw new ERR_INVALID_ARG_TYPE ( 'fd' , 'number' , fd ) ;
@@ -2346,8 +2359,10 @@ class ServerHttp2Stream extends Http2Stream {
2346
2359
}
2347
2360
2348
2361
let streamOptions = 0 ;
2349
- if ( options . waitForTrailers )
2362
+ if ( options . waitForTrailers ) {
2350
2363
streamOptions |= STREAM_OPTION_GET_TRAILERS ;
2364
+ this [ kState ] . flags |= STREAM_FLAGS_HAS_TRAILERS ;
2365
+ }
2351
2366
2352
2367
const session = this [ kSession ] ;
2353
2368
debug ( `Http2Stream ${ this [ kID ] } [Http2Session ` +
0 commit comments