@@ -50,6 +50,10 @@ internal sealed partial class Http2Connection : HttpConnectionBase
50
50
private readonly Channel < WriteQueueEntry > _writeChannel ;
51
51
private bool _lastPendingWriterShouldFlush ;
52
52
53
+ // Server-advertised SETTINGS_MAX_HEADER_LIST_SIZE
54
+ // https://www.rfc-editor.org/rfc/rfc9113.html#section-6.5.2-2.12.1
55
+ private uint _maxHeaderListSize = uint . MaxValue ; // Defaults to infinite
56
+
53
57
// This flag indicates that the connection is shutting down and cannot accept new requests, because of one of the following conditions:
54
58
// (1) We received a GOAWAY frame from the server
55
59
// (2) We have exhaustead StreamIds (i.e. _nextStream == MaxStreamId)
@@ -156,6 +160,14 @@ public Http2Connection(HttpConnectionPool pool, Stream stream)
156
160
_nextPingRequestTimestamp = Environment . TickCount64 + _keepAlivePingDelay ;
157
161
_keepAlivePingPolicy = _pool . Settings . _keepAlivePingPolicy ;
158
162
163
+ uint maxHeaderListSize = _pool . _lastSeenHttp2MaxHeaderListSize ;
164
+ if ( maxHeaderListSize > 0 )
165
+ {
166
+ // Previous connections to the same host advertised a limit.
167
+ // Use this as an initial value before we receive the SETTINGS frame.
168
+ _maxHeaderListSize = maxHeaderListSize ;
169
+ }
170
+
159
171
if ( HttpTelemetry . Log . IsEnabled ( ) )
160
172
{
161
173
HttpTelemetry . Log . Http20ConnectionEstablished ( ) ;
@@ -800,6 +812,8 @@ private void ProcessSettingsFrame(FrameHeader frameHeader, bool initialFrame = f
800
812
uint settingValue = BinaryPrimitives . ReadUInt32BigEndian ( settings ) ;
801
813
settings = settings . Slice ( 4 ) ;
802
814
815
+ if ( NetEventSource . Log . IsEnabled ( ) ) Trace ( $ "Applying setting { ( SettingId ) settingId } ={ settingValue } ") ;
816
+
803
817
switch ( ( SettingId ) settingId )
804
818
{
805
819
case SettingId . MaxConcurrentStreams :
@@ -825,6 +839,11 @@ private void ProcessSettingsFrame(FrameHeader frameHeader, bool initialFrame = f
825
839
// We don't actually store this value; we always send frames of the minimum size (16K).
826
840
break ;
827
841
842
+ case SettingId . MaxHeaderListSize :
843
+ _maxHeaderListSize = settingValue ;
844
+ _pool . _lastSeenHttp2MaxHeaderListSize = _maxHeaderListSize ;
845
+ break ;
846
+
828
847
default :
829
848
// All others are ignored because we don't care about them.
830
849
// Note, per RFC, unknown settings IDs should be ignored.
@@ -1332,17 +1351,19 @@ private void WriteBytes(ReadOnlySpan<byte> bytes, ref ArrayBuffer headerBuffer)
1332
1351
headerBuffer . Commit ( bytes . Length ) ;
1333
1352
}
1334
1353
1335
- private void WriteHeaderCollection ( HttpRequestMessage request , HttpHeaders headers , ref ArrayBuffer headerBuffer )
1354
+ private int WriteHeaderCollection ( HttpRequestMessage request , HttpHeaders headers , ref ArrayBuffer headerBuffer )
1336
1355
{
1337
1356
if ( NetEventSource . Log . IsEnabled ( ) ) Trace ( "" ) ;
1338
1357
1339
1358
if ( headers . HeaderStore is null )
1340
1359
{
1341
- return ;
1360
+ return 0 ;
1342
1361
}
1343
1362
1344
1363
HeaderEncodingSelector < HttpRequestMessage > ? encodingSelector = _pool . Settings . _requestHeaderEncodingSelector ;
1345
1364
1365
+ int headerListSize = headers . HeaderStore . Count * HeaderField . RfcOverhead ;
1366
+
1346
1367
ref string [ ] ? tmpHeaderValuesArray = ref t_headerValues ;
1347
1368
foreach ( KeyValuePair < HeaderDescriptor , object > header in headers . HeaderStore )
1348
1369
{
@@ -1360,6 +1381,10 @@ private void WriteHeaderCollection(HttpRequestMessage request, HttpHeaders heade
1360
1381
// The Connection, Upgrade and ProxyConnection headers are also not supported in HTTP2.
1361
1382
if ( knownHeader != KnownHeaders . Host && knownHeader != KnownHeaders . Connection && knownHeader != KnownHeaders . Upgrade && knownHeader != KnownHeaders . ProxyConnection )
1362
1383
{
1384
+ // The length of the encoded name may be shorter than the actual name.
1385
+ // Ensure that headerListSize is always >= of the actual size.
1386
+ headerListSize += knownHeader . Name . Length ;
1387
+
1363
1388
if ( header . Key . KnownHeader == KnownHeaders . TE )
1364
1389
{
1365
1390
// HTTP/2 allows only 'trailers' TE header. rfc7540 8.1.2.2
@@ -1400,6 +1425,8 @@ private void WriteHeaderCollection(HttpRequestMessage request, HttpHeaders heade
1400
1425
WriteLiteralHeader ( header . Key . Name , headerValues , valueEncoding , ref headerBuffer ) ;
1401
1426
}
1402
1427
}
1428
+
1429
+ return headerListSize ;
1403
1430
}
1404
1431
1405
1432
private void WriteHeaders ( HttpRequestMessage request , ref ArrayBuffer headerBuffer )
@@ -1430,9 +1457,9 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff
1430
1457
1431
1458
WriteIndexedHeader ( _stream is SslStream ? H2StaticTable . SchemeHttps : H2StaticTable . SchemeHttp , ref headerBuffer ) ;
1432
1459
1433
- if ( request . HasHeaders && request . Headers . Host != null )
1460
+ if ( request . HasHeaders && request . Headers . Host is string host )
1434
1461
{
1435
- WriteIndexedHeader ( H2StaticTable . Authority , request . Headers . Host , ref headerBuffer ) ;
1462
+ WriteIndexedHeader ( H2StaticTable . Authority , host , ref headerBuffer ) ;
1436
1463
}
1437
1464
else
1438
1465
{
@@ -1450,9 +1477,11 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff
1450
1477
WriteIndexedHeader ( H2StaticTable . PathSlash , pathAndQuery , ref headerBuffer ) ;
1451
1478
}
1452
1479
1480
+ int headerListSize = 3 * HeaderField . RfcOverhead ; // Method, Authority, Path
1481
+
1453
1482
if ( request . HasHeaders )
1454
1483
{
1455
- WriteHeaderCollection ( request , request . Headers , ref headerBuffer ) ;
1484
+ headerListSize += WriteHeaderCollection ( request , request . Headers , ref headerBuffer ) ;
1456
1485
}
1457
1486
1458
1487
// Determine cookies to send.
@@ -1462,9 +1491,9 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff
1462
1491
if ( cookiesFromContainer != string . Empty )
1463
1492
{
1464
1493
WriteBytes ( KnownHeaders . Cookie . Http2EncodedName , ref headerBuffer ) ;
1465
-
1466
1494
Encoding ? cookieEncoding = _pool . Settings . _requestHeaderEncodingSelector ? . Invoke ( KnownHeaders . Cookie . Name , request ) ;
1467
1495
WriteLiteralHeaderValue ( cookiesFromContainer , cookieEncoding , ref headerBuffer ) ;
1496
+ headerListSize += HttpKnownHeaderNames . Cookie . Length + HeaderField . RfcOverhead ;
1468
1497
}
1469
1498
}
1470
1499
@@ -1476,11 +1505,24 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff
1476
1505
{
1477
1506
WriteBytes ( KnownHeaders . ContentLength . Http2EncodedName , ref headerBuffer ) ;
1478
1507
WriteLiteralHeaderValue ( "0" , valueEncoding : null , ref headerBuffer ) ;
1508
+ headerListSize += HttpKnownHeaderNames . ContentLength . Length + HeaderField . RfcOverhead ;
1479
1509
}
1480
1510
}
1481
1511
else
1482
1512
{
1483
- WriteHeaderCollection ( request , request . Content . Headers , ref headerBuffer ) ;
1513
+ headerListSize += WriteHeaderCollection ( request , request . Content . Headers , ref headerBuffer ) ;
1514
+ }
1515
+
1516
+ // The headerListSize is an approximation of the total header length.
1517
+ // This is acceptable as long as the value is always >= the actual length.
1518
+ // We must avoid ever sending more than the server allowed.
1519
+ // This approach must be revisted if we ever support the dynamic table or compression when sending requests.
1520
+ headerListSize += headerBuffer . ActiveLength ;
1521
+
1522
+ uint maxHeaderListSize = _maxHeaderListSize ;
1523
+ if ( ( uint ) headerListSize > maxHeaderListSize )
1524
+ {
1525
+ throw new HttpRequestException ( SR . Format ( SR . net_http_request_headers_exceeded_length , maxHeaderListSize ) ) ;
1484
1526
}
1485
1527
}
1486
1528
@@ -1553,10 +1595,10 @@ private async ValueTask<Http2Stream> SendHeadersAsync(HttpRequestMessage request
1553
1595
// streams are created and started in order.
1554
1596
await PerformWriteAsync ( totalSize , ( thisRef : this , http2Stream , headerBytes , endStream : ( request . Content == null ) , mustFlush ) , static ( s , writeBuffer ) =>
1555
1597
{
1556
- if ( NetEventSource . Log . IsEnabled ( ) ) s . thisRef . Trace ( s . http2Stream . StreamId , $ "Started writing. Total header bytes={ s . headerBytes . Length } ") ;
1557
-
1558
1598
s . thisRef . AddStream ( s . http2Stream ) ;
1559
1599
1600
+ if ( NetEventSource . Log . IsEnabled ( ) ) s . thisRef . Trace ( s . http2Stream . StreamId , $ "Started writing. Total header bytes={ s . headerBytes . Length } ") ;
1601
+
1560
1602
Span < byte > span = writeBuffer . Span ;
1561
1603
1562
1604
// Copy the HEADERS frame.
0 commit comments