Skip to content

Commit 33cdc2e

Browse files
committed
HTTP/3: Use max field section size setting
1 parent b1e72cf commit 33cdc2e

File tree

19 files changed

+234
-43
lines changed

19 files changed

+234
-43
lines changed

src/Servers/Kestrel/Core/src/Http2Limits.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class Http2Limits
2525
/// <summary>
2626
/// Limits the number of concurrent request streams per HTTP/2 connection. Excess streams will be refused.
2727
/// <para>
28-
/// Value must be greater than 0, defaults to 100
28+
/// Value must be greater than 0, defaults to 100.
2929
/// </para>
3030
/// </summary>
3131
public int MaxStreamsPerConnection
@@ -45,7 +45,7 @@ public int MaxStreamsPerConnection
4545
/// <summary>
4646
/// Limits the size of the header compression tables, in octets, the HPACK encoder and decoder on the server can use.
4747
/// <para>
48-
/// Value must be greater than or equal to 0, defaults to 4096
48+
/// Value must be greater than or equal to 0, defaults to 4096.
4949
/// </para>
5050
/// </summary>
5151
public int HeaderTableSize
@@ -65,7 +65,7 @@ public int HeaderTableSize
6565
/// <summary>
6666
/// Indicates the size of the largest frame payload that is allowed to be received, in octets. The size must be between 2^14 and 2^24-1.
6767
/// <para>
68-
/// Value must be between 2^14 and 2^24, defaults to 2^14 (16,384)
68+
/// Value must be between 2^14 and 2^24, defaults to 2^14 (16,384).
6969
/// </para>
7070
/// </summary>
7171
public int MaxFrameSize
@@ -85,7 +85,7 @@ public int MaxFrameSize
8585
/// <summary>
8686
/// Indicates the size of the maximum allowed size of a request header field sequence. This limit applies to both name and value sequences in their compressed and uncompressed representations.
8787
/// <para>
88-
/// Value must be greater than 0, defaults to 2^14 (16,384)
88+
/// Value must be greater than 0, defaults to 2^14 (16,384).
8989
/// </para>
9090
/// </summary>
9191
public int MaxRequestHeaderFieldSize

src/Servers/Kestrel/Core/src/Http3Limits.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
1010
/// </summary>
1111
public class Http3Limits
1212
{
13+
internal const int DefaultMaxRequestHeaderFieldSize = 16 * 1024;
14+
1315
private int _headerTableSize;
14-
private int _maxRequestHeaderFieldSize = 8192;
16+
private int _maxRequestHeaderFieldSize = DefaultMaxRequestHeaderFieldSize;
1517

1618
/// <summary>
1719
/// Limits the size of the header compression table, in octets, the QPACK decoder on the server can use.
@@ -37,7 +39,7 @@ internal int HeaderTableSize
3739
/// <summary>
3840
/// Indicates the size of the maximum allowed size of a request header field sequence. This limit applies to both name and value sequences in their compressed and uncompressed representations.
3941
/// <para>
40-
/// Value must be greater than 0, defaults to 8192
42+
/// Value must be greater than 0, defaults to 2^14 (16,384).
4143
/// </para>
4244
/// </summary>
4345
public int MaxRequestHeaderFieldSize

src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ internal class Http3Connection : IHttp3StreamLifetimeHandler, IRequestProcessor
3939
private int _activeRequestCount;
4040

4141
private readonly Http3PeerSettings _serverSettings = new Http3PeerSettings();
42+
private readonly Http3PeerSettings _clientSettings = new Http3PeerSettings();
4243
private readonly StreamCloseAwaitable _streamCompletionAwaitable = new StreamCloseAwaitable();
4344
private readonly IProtocolErrorCodeFeature _errorCodeFeature;
4445

@@ -53,7 +54,7 @@ public Http3Connection(HttpMultiplexedConnectionContext context)
5354
var httpLimits = context.ServiceContext.ServerOptions.Limits;
5455

5556
_serverSettings.HeaderTableSize = (uint)httpLimits.Http3.HeaderTableSize;
56-
_serverSettings.MaxRequestHeaderFieldSize = (uint)httpLimits.Http3.MaxRequestHeaderFieldSize;
57+
_serverSettings.MaxRequestHeaderFieldSectionSize = (uint)httpLimits.MaxRequestHeadersTotalSize;
5758
}
5859

5960
private void UpdateHighestStreamId(long streamId)
@@ -260,6 +261,7 @@ public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> appl
260261
streamContext.Transport,
261262
_streamLifetimeHandler,
262263
streamContext,
264+
_clientSettings,
263265
_serverSettings);
264266
httpConnectionContext.TimeoutControl = _context.TimeoutControl;
265267

@@ -423,6 +425,7 @@ private async ValueTask<Http3ControlStream> CreateNewUnidirectionalStreamAsync<T
423425
streamContext.Transport,
424426
_streamLifetimeHandler,
425427
streamContext,
428+
_clientSettings,
426429
_serverSettings);
427430
httpConnectionContext.TimeoutControl = _context.TimeoutControl;
428431

@@ -522,6 +525,7 @@ void IHttp3StreamLifetimeHandler.OnInboundControlStreamSetting(Http3SettingType
522525
case Http3SettingType.QPackMaxTableCapacity:
523526
break;
524527
case Http3SettingType.MaxFieldSectionSize:
528+
_clientSettings.MaxRequestHeaderFieldSectionSize = (uint)value;
525529
break;
526530
case Http3SettingType.QPackBlockedStreams:
527531
break;

src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public Http3ControlStream(Http3StreamContext context)
4242
{
4343
var httpLimits = context.ServiceContext.ServerOptions.Limits;
4444
_context = context;
45-
_serverPeerSettings = context.ServerSettings;
45+
_serverPeerSettings = context.ServerPeerSettings;
4646
_streamIdFeature = context.ConnectionFeatures.Get<IStreamIdFeature>()!;
4747
_errorCodeFeature = context.ConnectionFeatures.Get<IProtocolErrorCodeFeature>()!;
4848
_headerType = -1;
@@ -55,7 +55,9 @@ public Http3ControlStream(Http3StreamContext context)
5555
context.ConnectionId,
5656
context.MemoryPool,
5757
context.ServiceContext.Log,
58-
_streamIdFeature);
58+
_streamIdFeature,
59+
context.ClientPeerSettings,
60+
this);
5961
}
6062

6163
private void OnStreamClosed()
@@ -295,15 +297,15 @@ private ValueTask ProcessSettingsFrameAsync(ReadOnlySequence<byte> payload)
295297

296298
while (true)
297299
{
298-
var id = VariableLengthIntegerHelper.GetInteger(payload, out var consumed, out var examinded);
300+
var id = VariableLengthIntegerHelper.GetInteger(payload, out var consumed, out _);
299301
if (id == -1)
300302
{
301303
break;
302304
}
303305

304306
payload = payload.Slice(consumed);
305307

306-
var value = VariableLengthIntegerHelper.GetInteger(payload, out consumed, out examinded);
308+
var value = VariableLengthIntegerHelper.GetInteger(payload, out consumed, out _);
307309
if (id == -1)
308310
{
309311
break;

src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ internal class Http3FrameWriter
3232
private readonly MemoryPool<byte> _memoryPool;
3333
private readonly IKestrelTrace _log;
3434
private readonly IStreamIdFeature _streamIdFeature;
35+
private readonly IHttp3Stream _http3Stream;
3536
private readonly Http3RawFrame _outgoingFrame;
3637
private readonly TimingPipeFlusher _flusher;
3738

@@ -45,7 +46,7 @@ internal class Http3FrameWriter
4546

4647
//private int _unflushedBytes;
4748

48-
public Http3FrameWriter(PipeWriter output, ConnectionContext connectionContext, ITimeoutControl timeoutControl, MinDataRate? minResponseDataRate, string connectionId, MemoryPool<byte> memoryPool, IKestrelTrace log, IStreamIdFeature streamIdFeature)
49+
public Http3FrameWriter(PipeWriter output, ConnectionContext connectionContext, ITimeoutControl timeoutControl, MinDataRate? minResponseDataRate, string connectionId, MemoryPool<byte> memoryPool, IKestrelTrace log, IStreamIdFeature streamIdFeature, Http3PeerSettings clientPeerSettings, IHttp3Stream http3Stream)
4950
{
5051
_outputWriter = output;
5152
_connectionContext = connectionContext;
@@ -55,9 +56,19 @@ public Http3FrameWriter(PipeWriter output, ConnectionContext connectionContext,
5556
_memoryPool = memoryPool;
5657
_log = log;
5758
_streamIdFeature = streamIdFeature;
59+
_http3Stream = http3Stream;
5860
_outgoingFrame = new Http3RawFrame();
5961
_flusher = new TimingPipeFlusher(_outputWriter, timeoutControl, log);
6062
_headerEncodingBuffer = new byte[_maxFrameSize];
63+
_qpackEncoder = new QPackEncoder();
64+
65+
// Note that QPack encoder doesn't react to settings change during a stream.
66+
// Unlikely to be a problem in practice:
67+
// - Settings rarely change after the start of a connection.
68+
// - Response header size limits are a best-effort requirement in the spec.
69+
_qpackEncoder.MaxTotalHeaderSize = clientPeerSettings.MaxRequestHeaderFieldSectionSize > int.MaxValue
70+
? int.MaxValue
71+
: (int)clientPeerSettings.MaxRequestHeaderFieldSectionSize;
6172
}
6273

6374
public void UpdateMaxFrameSize(uint maxFrameSize)
@@ -257,7 +268,7 @@ internal static int WriteHeader(Http3RawFrame frame, PipeWriter output)
257268
return totalLength;
258269
}
259270

260-
public ValueTask<FlushResult> WriteResponseTrailers(HttpResponseTrailers headers)
271+
public ValueTask<FlushResult> WriteResponseTrailersAsync(long streamId, HttpResponseTrailers headers)
261272
{
262273
lock (_writeLock)
263274
{
@@ -275,10 +286,10 @@ public ValueTask<FlushResult> WriteResponseTrailers(HttpResponseTrailers headers
275286
var done = QPackHeaderWriter.BeginEncode(_headersEnumerator, buffer, out var payloadLength);
276287
FinishWritingHeaders(payloadLength, done);
277288
}
278-
catch (QPackEncodingException)
289+
catch (QPackEncodingException ex)
279290
{
280-
//_log.HPackEncodingError(_connectionId, streamId, hex);
281-
//_http3Stream.Abort(new ConnectionAbortedException(hex.Message, hex));
291+
_log.QPackEncodingError(_connectionId, streamId, ex);
292+
_http3Stream.Abort(new ConnectionAbortedException(ex.Message, ex), Http3ErrorCode.InternalError);
282293
}
283294

284295
return TimeFlushUnsynchronizedAsync();
@@ -327,11 +338,10 @@ internal void WriteResponseHeaders(int statusCode, IHeaderDictionary headers)
327338
var done = QPackHeaderWriter.BeginEncode(statusCode, _headersEnumerator, buffer, out var payloadLength);
328339
FinishWritingHeaders(payloadLength, done);
329340
}
330-
catch (QPackEncodingException hex)
341+
catch (QPackEncodingException ex)
331342
{
332-
// TODO figure out how to abort the stream here.
333-
//_http3Stream.Abort(new ConnectionAbortedException(hex.Message, hex));
334-
throw new InvalidOperationException(hex.Message, hex); // Report the error to the user if this was the first write.
343+
_http3Stream.Abort(new ConnectionAbortedException(ex.Message, ex), Http3ErrorCode.InternalError);
344+
throw new InvalidOperationException(ex.Message, ex); // Report the error to the user if this was the first write.
335345
}
336346
}
337347
}

src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ private async ValueTask<FlushResult> ProcessDataWrites()
352352
}
353353

354354
_stream.ResponseTrailers.SetReadOnly();
355-
flushResult = await _frameWriter.WriteResponseTrailers(_stream.ResponseTrailers);
355+
flushResult = await _frameWriter.WriteResponseTrailersAsync(_stream.StreamId, _stream.ResponseTrailers);
356356
}
357357
else if (readResult.IsCompleted)
358358
{

src/Servers/Kestrel/Core/src/Internal/Http3/Http3PeerSettings.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ internal class Http3PeerSettings
1212
public const uint DefaultMaxRequestHeaderFieldSize = uint.MaxValue;
1313

1414
public uint HeaderTableSize { get; internal set; } = DefaultHeaderTableSize;
15-
public uint MaxRequestHeaderFieldSize { get; internal set; } = DefaultMaxRequestHeaderFieldSize;
15+
public uint MaxRequestHeaderFieldSectionSize { get; internal set; } = DefaultMaxRequestHeaderFieldSize;
1616

1717
// Gets the settings that are different from the protocol defaults (as opposed to the server defaults).
1818
internal List<Http3PeerSetting> GetNonProtocolDefaults()
@@ -26,9 +26,9 @@ internal List<Http3PeerSetting> GetNonProtocolDefaults()
2626
list.Add(new Http3PeerSetting(Http3SettingType.QPackMaxTableCapacity, HeaderTableSize));
2727
}
2828

29-
if (MaxRequestHeaderFieldSize != DefaultMaxRequestHeaderFieldSize)
29+
if (MaxRequestHeaderFieldSectionSize != DefaultMaxRequestHeaderFieldSize)
3030
{
31-
list.Add(new Http3PeerSetting(Http3SettingType.MaxFieldSectionSize, MaxRequestHeaderFieldSize));
31+
list.Add(new Http3PeerSetting(Http3SettingType.MaxFieldSectionSize, MaxRequestHeaderFieldSectionSize));
3232
}
3333

3434
return list;

src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ public Http3Stream(Http3StreamContext context)
7171
context.ConnectionId,
7272
context.MemoryPool,
7373
context.ServiceContext.Log,
74-
_streamIdFeature);
74+
_streamIdFeature,
75+
context.ClientPeerSettings,
76+
this);
7577

7678
// ResponseHeaders aren't set, kind of ugly that we need to reset.
7779
Reset();
@@ -485,8 +487,16 @@ private async Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext>
485487
_requestHeaderParsingState = RequestHeaderParsingState.Trailers;
486488
}
487489

488-
QPackDecoder.Decode(payload, handler: this);
489-
QPackDecoder.Reset();
490+
try
491+
{
492+
QPackDecoder.Decode(payload, handler: this);
493+
QPackDecoder.Reset();
494+
}
495+
catch (QPackDecodingException ex)
496+
{
497+
Log.QPackDecodingError(ConnectionId, StreamId, ex);
498+
throw new Http3StreamErrorException(ex.Message, Http3ErrorCode.InternalError);
499+
}
490500

491501
switch (_requestHeaderParsingState)
492502
{

src/Servers/Kestrel/Core/src/Internal/Http3StreamContext.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,18 @@ public Http3StreamContext(
2424
IDuplexPipe transport,
2525
IHttp3StreamLifetimeHandler streamLifetimeHandler,
2626
ConnectionContext streamContext,
27-
Http3PeerSettings settings) : base(connectionId, protocols, connectionContext, serviceContext, connectionFeatures, memoryPool, localEndPoint, remoteEndPoint, transport)
27+
Http3PeerSettings clientPeerSettings,
28+
Http3PeerSettings serverPeerSettings) : base(connectionId, protocols, connectionContext, serviceContext, connectionFeatures, memoryPool, localEndPoint, remoteEndPoint, transport)
2829
{
2930
StreamLifetimeHandler = streamLifetimeHandler;
3031
StreamContext = streamContext;
31-
ServerSettings = settings;
32+
ClientPeerSettings = clientPeerSettings;
33+
ServerPeerSettings = serverPeerSettings;
3234
}
3335

3436
public IHttp3StreamLifetimeHandler StreamLifetimeHandler { get; }
3537
public ConnectionContext StreamContext { get; }
36-
public Http3PeerSettings ServerSettings { get; }
38+
public Http3PeerSettings ClientPeerSettings { get; }
39+
public Http3PeerSettings ServerPeerSettings { get; }
3740
}
3841
}

src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Net.Http;
66
using System.Net.Http.HPack;
7+
using System.Net.Http.QPack;
78
using Microsoft.AspNetCore.Connections;
89
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
910
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3;
@@ -92,5 +93,9 @@ internal interface IKestrelTrace : ILogger
9293
void Http3FrameReceived(string connectionId, long streamId, Http3RawFrame frame);
9394

9495
void Http3FrameSending(string connectionId, long streamId, Http3RawFrame frame);
96+
97+
void QPackDecodingError(string connectionId, long streamId, QPackDecodingException ex);
98+
99+
void QPackEncodingError(string connectionId, long streamId, QPackEncodingException ex);
95100
}
96101
}

0 commit comments

Comments
 (0)