Skip to content

Commit a53e63d

Browse files
committed
HTTP/3: Use max field section size setting
1 parent 4fc4748 commit a53e63d

File tree

15 files changed

+194
-76
lines changed

15 files changed

+194
-76
lines changed

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

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
1111
public class Http3Limits
1212
{
1313
private int _headerTableSize;
14-
private int _maxRequestHeaderFieldSize = 8192;
1514

1615
/// <summary>
1716
/// Limits the size of the header compression table, in octets, the QPACK decoder on the server can use.
@@ -33,25 +32,5 @@ internal int HeaderTableSize
3332
_headerTableSize = value;
3433
}
3534
}
36-
37-
/// <summary>
38-
/// 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.
39-
/// <para>
40-
/// Value must be greater than 0, defaults to 8192
41-
/// </para>
42-
/// </summary>
43-
public int MaxRequestHeaderFieldSize
44-
{
45-
get => _maxRequestHeaderFieldSize;
46-
set
47-
{
48-
if (value <= 0)
49-
{
50-
throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.GreaterThanZeroRequired);
51-
}
52-
53-
_maxRequestHeaderFieldSize = value;
54-
}
55-
}
5635
}
5736
}

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: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
2222
internal class Http3FrameWriter
2323
{
2424
private readonly object _writeLock = new object();
25-
private readonly QPackEncoder _qpackEncoder = new QPackEncoder();
25+
private readonly QPackEncoder _qpackEncoder;
2626

2727
private readonly PipeWriter _outputWriter;
2828
private readonly ConnectionContext _connectionContext;
@@ -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,16 @@ 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+
// TODO(JamesNK): QPack encoder doesn't react to settings change during a stream
66+
_qpackEncoder.MaxTotalHeaderSize = clientPeerSettings.MaxRequestHeaderFieldSectionSize > int.MaxValue
67+
? int.MaxValue
68+
: (int)clientPeerSettings.MaxRequestHeaderFieldSectionSize;
6169
}
6270

6371
public void UpdateMaxFrameSize(uint maxFrameSize)
@@ -273,10 +281,10 @@ public ValueTask<FlushResult> WriteResponseTrailers(HttpResponseTrailers headers
273281
var done = _qpackEncoder.BeginEncode(EnumerateHeaders(headers), buffer, out var payloadLength);
274282
FinishWritingHeaders(payloadLength, done);
275283
}
276-
catch (QPackEncodingException)
284+
catch (QPackEncodingException ex)
277285
{
278286
//_log.HPackEncodingError(_connectionId, streamId, hex);
279-
//_http3Stream.Abort(new ConnectionAbortedException(hex.Message, hex));
287+
_http3Stream.Abort(new ConnectionAbortedException(ex.Message, ex), Http3ErrorCode.InternalError);
280288
}
281289

282290
return TimeFlushUnsynchronizedAsync();
@@ -323,11 +331,10 @@ internal void WriteResponseHeaders(int statusCode, IHeaderDictionary headers)
323331
var done = _qpackEncoder.BeginEncode(statusCode, EnumerateHeaders(headers), buffer, out var payloadLength);
324332
FinishWritingHeaders(payloadLength, done);
325333
}
326-
catch (QPackEncodingException hex)
334+
catch (QPackEncodingException ex)
327335
{
328-
// TODO figure out how to abort the stream here.
329-
//_http3Stream.Abort(new ConnectionAbortedException(hex.Message, hex));
330-
throw new InvalidOperationException(hex.Message, hex); // Report the error to the user if this was the first write.
336+
_http3Stream.Abort(new ConnectionAbortedException(ex.Message, ex), Http3ErrorCode.InternalError);
337+
throw new InvalidOperationException(ex.Message, ex); // Report the error to the user if this was the first write.
331338
}
332339
}
333340
}

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: 8 additions & 2 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();
@@ -83,7 +85,11 @@ public Http3Stream(Http3StreamContext context)
8385
context.ServiceContext.Log);
8486
RequestBodyPipe = CreateRequestBodyPipe(64 * 1024); // windowSize?
8587
Output = _http3Output;
86-
QPackDecoder = new QPackDecoder(_context.ServiceContext.ServerOptions.Limits.Http3.MaxRequestHeaderFieldSize);
88+
89+
// TODO(JamesNK): What is the purpose of max header length and why measure in QPackDecoder?
90+
// I think the meaning of SETTINGS_MAX_FIELD_SECTION_SIZE may have been misunderstood to apply per
91+
// header rather than all headers that make up the section.
92+
QPackDecoder = new QPackDecoder(int.MaxValue);
8793
}
8894

8995
public long? InputRemaining { get; internal set; }

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/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@
9595
*REMOVED*~static Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(this Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions listenOptions, string fileName, string password) -> Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions
9696
*REMOVED*~static Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(this Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions listenOptions, string fileName, string password, System.Action<Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions> configureOptions) -> Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions
9797
*REMOVED*~static Microsoft.AspNetCore.Server.Kestrel.Https.CertificateLoader.LoadFromStoreCert(string subject, string storeName, System.Security.Cryptography.X509Certificates.StoreLocation storeLocation, bool allowInvalid) -> System.Security.Cryptography.X509Certificates.X509Certificate2
98+
*REMOVED*Microsoft.AspNetCore.Server.Kestrel.Core.Http3Limits.MaxRequestHeaderFieldSize.get -> int
99+
*REMOVED*Microsoft.AspNetCore.Server.Kestrel.Core.Http3Limits.MaxRequestHeaderFieldSize.set -> void
98100
Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode.DelayCertificate = 3 -> Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode
99101
static Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(this Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions! listenOptions, Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions! httpsOptions) -> Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions!
100102
static Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(this Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions! listenOptions, System.Action<Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions!>! configureOptions) -> Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions!

src/Servers/Kestrel/Core/test/Http3FrameWriterTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,17 @@ public async Task WriteSettings_OneSettingsWrittenWithKestrelDefaults()
5555
var pipe = new Pipe(new PipeOptions(_dirtyMemoryPool, PipeScheduler.Inline, PipeScheduler.Inline));
5656
var frameWriter = CreateFrameWriter(pipe);
5757

58-
var limits = new Http3Limits();
58+
var kestrelLimits = new KestrelServerLimits();
5959
var settings = new Http3PeerSettings();
60-
settings.HeaderTableSize = (uint)limits.HeaderTableSize;
61-
settings.MaxRequestHeaderFieldSize = (uint)limits.MaxRequestHeaderFieldSize;
60+
settings.HeaderTableSize = (uint)kestrelLimits.Http3.HeaderTableSize;
61+
settings.MaxRequestHeaderFieldSectionSize = (uint)kestrelLimits.MaxRequestHeadersTotalSize;
6262

6363
await frameWriter.WriteSettingsAsync(settings.GetNonProtocolDefaults());
6464

6565
// variable length ints make it so the results isn't know without knowing the values
6666
var payload = await pipe.Reader.ReadForLengthAsync(5);
6767

68-
Assert.Equal(new byte[] { 0x04, 0x03, 0x06, 0x60, 0x00 }, payload.ToArray());
68+
Assert.Equal(new byte[] { 0x04, 0x05, 0x06, 0x80, 0x00 }, payload.ToArray());
6969
}
7070

7171
[Fact]
@@ -76,7 +76,7 @@ public async Task WriteSettings_TwoSettingsWritten()
7676

7777
var settings = new Http3PeerSettings();
7878
settings.HeaderTableSize = 1234;
79-
settings.MaxRequestHeaderFieldSize = 567890;
79+
settings.MaxRequestHeaderFieldSectionSize = 567890;
8080

8181
await frameWriter.WriteSettingsAsync(settings.GetNonProtocolDefaults());
8282

@@ -88,7 +88,7 @@ public async Task WriteSettings_TwoSettingsWritten()
8888

8989
private Http3FrameWriter CreateFrameWriter(Pipe pipe)
9090
{
91-
return new Http3FrameWriter(pipe.Writer, null, null, null, null, _dirtyMemoryPool, null, Mock.Of<IStreamIdFeature>());
91+
return new Http3FrameWriter(pipe.Writer, null, null, null, null, _dirtyMemoryPool, null, Mock.Of<IStreamIdFeature>(), new Http3PeerSettings(), null);
9292
}
9393
}
9494
}

src/Servers/Kestrel/shared/test/TestContextFactory.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ public static Http3StreamContext CreateHttp3StreamContext(
188188
transport: transport,
189189
streamLifetimeHandler: streamLifetimeHandler,
190190
streamContext: null,
191-
settings: null
191+
clientPeerSettings: new Http3PeerSettings(),
192+
serverPeerSettings: null
192193
);
193194
context.TimeoutControl = timeoutControl;
194195

0 commit comments

Comments
 (0)