diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs index 8aa61f383167..1ae6112104d9 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs @@ -63,6 +63,8 @@ internal abstract partial class HttpProtocol : IHttpResponseControl private string? _requestId; private int _requestHeadersParsed; + // See MaxRequestHeaderCount, enforced during parsing and may be more relaxed to avoid connection faults. + protected int _eagerRequestHeadersParsedLimit; private long _responseBytesWritten; @@ -107,6 +109,7 @@ public void Initialize(HttpConnectionContext context) public long? MaxRequestBodySize { get; set; } public MinDataRate? MinRequestBodyDataRate { get; set; } public bool AllowSynchronousIO { get; set; } + protected int RequestHeadersParsed => _requestHeadersParsed; /// /// The request id. @@ -416,6 +419,7 @@ public void Reset() Output?.Reset(); _requestHeadersParsed = 0; + _eagerRequestHeadersParsedLimit = ServerOptions.Limits.MaxRequestHeaderCount; _responseBytesWritten = 0; @@ -546,7 +550,7 @@ public void OnTrailer(ReadOnlySpan name, ReadOnlySpan value) private void IncrementRequestHeadersCount() { _requestHeadersParsed++; - if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount) + if (_requestHeadersParsed > _eagerRequestHeadersParsedLimit) { KestrelBadHttpRequestException.Throw(RequestRejectionReason.TooManyHeaders); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 668bfe8faffa..d194ed19c61b 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -1130,6 +1130,8 @@ private void StartStream() try { + _currentHeadersStream.TotalParsedHeaderSize = _totalParsedHeaderSize; + // This must be initialized before we offload the request or else we may start processing request body frames without it. _currentHeadersStream.InputRemaining = _currentHeadersStream.RequestHeaders.ContentLength; @@ -1412,8 +1414,10 @@ private void OnHeaderCore(HeaderType headerType, int? staticTableIndex, ReadOnly // https://tools.ietf.org/html/rfc7540#section-6.5.2 // "The value is based on the uncompressed size of header fields, including the length of the name and value in octets plus an overhead of 32 octets for each header field."; - _totalParsedHeaderSize += HeaderField.RfcOverhead + name.Length + value.Length; - if (_totalParsedHeaderSize > _context.ServiceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize) + // We don't include the 32 byte overhead hear so we can accept a little more than the advertised limit. + _totalParsedHeaderSize += name.Length + value.Length; + // Allow a 2x grace before aborting the connection. We'll check the size limit again later where we can send a 431. + if (_totalParsedHeaderSize > _context.ServiceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize * 2) { throw new Http2ConnectionErrorException(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, Http2ErrorCode.PROTOCOL_ERROR); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs index 6df8039ae77e..9e9ff968a3a7 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs @@ -25,6 +25,8 @@ internal abstract partial class Http2Stream : HttpProtocol, IThreadPoolWorkItem, private bool _decrementCalled; + public int TotalParsedHeaderSize { get; set; } + public Pipe RequestBodyPipe { get; private set; } = default!; internal long DrainExpirationTicks { get; set; } @@ -41,6 +43,9 @@ public void Initialize(Http2StreamContext context) InputRemaining = null; RequestBodyStarted = false; DrainExpirationTicks = 0; + TotalParsedHeaderSize = 0; + // Allow up to 2x during parsing, enforce the hard limit after when we can preserve the connection. + _eagerRequestHeadersParsedLimit = ServerOptions.Limits.MaxRequestHeaderCount * 2; _context = context; @@ -198,6 +203,18 @@ protected override bool TryParseRequest(ReadResult result, out bool endConnectio // do the reading from a pipeline, nor do we use endConnection to report connection-level errors. endConnection = !TryValidatePseudoHeaders(); + // 431 if the headers are too large + if (TotalParsedHeaderSize > ServerOptions.Limits.MaxRequestHeadersTotalSize) + { + KestrelBadHttpRequestException.Throw(RequestRejectionReason.HeadersExceedMaxTotalSize); + } + + // 431 if we received too many headers + if (RequestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount) + { + KestrelBadHttpRequestException.Throw(RequestRejectionReason.TooManyHeaders); + } + // Suppress pseudo headers from the public headers collection. HttpRequestHeaders.ClearPseudoRequestHeaders(); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index ea9a8ce96e3d..aaf4cd8e645f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -96,6 +96,8 @@ public void Initialize(Http3StreamContext context) _requestHeaderParsingState = default; _parsedPseudoHeaderFields = default; _totalParsedHeaderSize = 0; + // Allow up to 2x during parsing, enforce the hard limit after when we can preserve the connection. + _eagerRequestHeadersParsedLimit = ServerOptions.Limits.MaxRequestHeaderCount * 2; _isMethodConnect = false; _completionState = default; StreamTimeoutTicks = 0; @@ -275,10 +277,12 @@ private void AppendHeader(ReadOnlySpan name, ReadOnlySpan value) private void OnHeaderCore(HeaderType headerType, int? staticTableIndex, ReadOnlySpan name, ReadOnlySpan value) { - // https://tools.ietf.org/html/rfc7540#section-6.5.2 + // https://httpwg.org/specs/rfc9114.html#rfc.section.4.2.2 // "The value is based on the uncompressed size of header fields, including the length of the name and value in octets plus an overhead of 32 octets for each header field."; - _totalParsedHeaderSize += HeaderField.RfcOverhead + name.Length + value.Length; - if (_totalParsedHeaderSize > _context.ServiceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize) + // We don't include the 32 byte overhead hear so we can accept a little more than the advertised limit. + _totalParsedHeaderSize += name.Length + value.Length; + // Allow a 2x grace before aborting the stream. We'll check the size limit again later where we can send a 431. + if (_totalParsedHeaderSize > ServerOptions.Limits.MaxRequestHeadersTotalSize * 2) { throw new Http3StreamErrorException(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, Http3ErrorCode.RequestRejected); } @@ -939,6 +943,18 @@ protected override bool TryParseRequest(ReadResult result, out bool endConnectio { endConnection = !TryValidatePseudoHeaders(); + // 431 if the headers are too large + if (_totalParsedHeaderSize > ServerOptions.Limits.MaxRequestHeadersTotalSize) + { + KestrelBadHttpRequestException.Throw(RequestRejectionReason.HeadersExceedMaxTotalSize); + } + + // 431 if we received too many headers + if (RequestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount) + { + KestrelBadHttpRequestException.Throw(RequestRejectionReason.TooManyHeaders); + } + // Suppress pseudo headers from the public headers collection. HttpRequestHeaders.ClearPseudoRequestHeaders(); diff --git a/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs b/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs index 8091afec1044..cd1cc5b208bd 100644 --- a/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs +++ b/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs @@ -89,6 +89,7 @@ public async Task TakeMessageHeadersThrowsWhenHeadersExceedCountLimit() { const string headerLines = "Header-1: value1\r\nHeader-2: value2\r\n"; _serviceContext.ServerOptions.Limits.MaxRequestHeaderCount = 1; + _http1Connection.Initialize(_http1ConnectionContext); await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n")); var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; diff --git a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs index 75dae74dcd58..f4ecc6a6a598 100644 --- a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs +++ b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs @@ -650,7 +650,7 @@ internal async Task WaitForStreamErrorAsync(Http3ErrorCode protocolError, Action internal class Http3RequestHeaderHandler { - public readonly byte[] HeaderEncodingBuffer = new byte[64 * 1024]; + public readonly byte[] HeaderEncodingBuffer = new byte[96 * 1024]; public readonly QPackDecoder QpackDecoder = new QPackDecoder(8192); public readonly Dictionary DecodedHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); } @@ -699,9 +699,8 @@ public async Task SendHeadersAsync(Http3HeadersEnumerator headers, bool endStrea var done = QPackHeaderWriter.BeginEncodeHeaders(headers, buffer.Span, ref headersTotalSize, out var length); if (!done) { - throw new InvalidOperationException("Headers not sent."); + throw new InvalidOperationException("The headers are too large."); } - await SendFrameAsync(Http3FrameType.Headers, buffer.Slice(0, length), endStream); } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index 89f6de2cc0fe..61f5bda90ef4 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -2724,7 +2724,7 @@ await WaitForConnectionErrorAsync( [Fact] public Task HEADERS_Received_HeaderBlockOverLimit_ConnectionError() { - // > 32kb + // > 32kb * 2 to exceed graceful handling limit var headers = new[] { new KeyValuePair(InternalHeaderNames.Method, "GET"), @@ -2738,6 +2738,14 @@ public Task HEADERS_Received_HeaderBlockOverLimit_ConnectionError() new KeyValuePair("f", _4kHeaderValue), new KeyValuePair("g", _4kHeaderValue), new KeyValuePair("h", _4kHeaderValue), + new KeyValuePair("i", _4kHeaderValue), + new KeyValuePair("j", _4kHeaderValue), + new KeyValuePair("k", _4kHeaderValue), + new KeyValuePair("l", _4kHeaderValue), + new KeyValuePair("m", _4kHeaderValue), + new KeyValuePair("n", _4kHeaderValue), + new KeyValuePair("o", _4kHeaderValue), + new KeyValuePair("p", _4kHeaderValue), }; return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, CoreStrings.BadRequest_HeadersExceedMaxTotalSize); @@ -2746,7 +2754,7 @@ public Task HEADERS_Received_HeaderBlockOverLimit_ConnectionError() [Fact] public Task HEADERS_Received_TooManyHeaders_ConnectionError() { - // > MaxRequestHeaderCount (100) + // > MaxRequestHeaderCount (100) * 2 to exceed graceful handling limit var headers = new List>(); headers.AddRange(new[] { @@ -2754,7 +2762,7 @@ public Task HEADERS_Received_TooManyHeaders_ConnectionError() new KeyValuePair(InternalHeaderNames.Path, "/"), new KeyValuePair(InternalHeaderNames.Scheme, "http"), }); - for (var i = 0; i < 100; i++) + for (var i = 0; i < 200; i++) { headers.Add(new KeyValuePair(i.ToString(CultureInfo.InvariantCulture), i.ToString(CultureInfo.InvariantCulture))); } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index b5e37940abea..4ec15cf429f2 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; @@ -797,6 +798,77 @@ public async Task HEADERS_Received_MaxRequestLineSize_Reset() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); } + [Fact] + public async Task HEADERS_Received_MaxRequestHeadersTotalSize_431() + { + // > 32kb + var headers = new[] + { + new KeyValuePair(InternalHeaderNames.Method, "GET"), + new KeyValuePair(InternalHeaderNames.Path, "/"), + new KeyValuePair(InternalHeaderNames.Scheme, "http"), + new KeyValuePair("a", _4kHeaderValue), + new KeyValuePair("b", _4kHeaderValue), + new KeyValuePair("c", _4kHeaderValue), + new KeyValuePair("d", _4kHeaderValue), + new KeyValuePair("e", _4kHeaderValue), + new KeyValuePair("f", _4kHeaderValue), + new KeyValuePair("g", _4kHeaderValue), + new KeyValuePair("h", _4kHeaderValue), + }; + await InitializeConnectionAsync(_notImplementedApp); + + await StartStreamAsync(1, headers, endStream: true); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 40, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), + withStreamId: 1); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + _hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this); + + Assert.Equal(3, _decodedHeaders.Count); + Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("431", _decodedHeaders[InternalHeaderNames.Status]); + Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]); + } + + [Fact] + public async Task HEADERS_Received_MaxRequestHeaderCount_431() + { + // > 100 headers + var headers = new List>() + { + new KeyValuePair(InternalHeaderNames.Method, "GET"), + new KeyValuePair(InternalHeaderNames.Path, "/"), + new KeyValuePair(InternalHeaderNames.Scheme, "http"), + }; + for (var i = 0; i < 101; i++) + { + var text = i.ToString(CultureInfo.InvariantCulture); + headers.Add(new KeyValuePair(text, text)); + } + await InitializeConnectionAsync(_notImplementedApp); + + await StartStreamAsync(1, headers, endStream: true); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 40, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), + withStreamId: 1); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + _hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this); + + Assert.Equal(3, _decodedHeaders.Count); + Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("431", _decodedHeaders[InternalHeaderNames.Status]); + Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]); + } + [Fact] public async Task ContentLength_Received_SingleDataFrame_Verified() { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index ec8627bab14b..904e38992e71 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -134,6 +134,7 @@ protected static IEnumerable> ReadRateRequestHeader protected readonly TaskCompletionSource _closedStateReached = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); protected readonly RequestDelegate _noopApplication; + protected readonly RequestDelegate _notImplementedApp; protected readonly RequestDelegate _readHeadersApplication; protected readonly RequestDelegate _readTrailersApplication; protected readonly RequestDelegate _bufferingApplication; @@ -176,6 +177,7 @@ public Http2TestBase() }); _noopApplication = context => Task.CompletedTask; + _notImplementedApp = _ => throw new NotImplementedException(); _readHeadersApplication = context => { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs index 884d3d9a4f4a..79d0668d135d 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs @@ -2300,7 +2300,7 @@ await requestStream.WaitForStreamErrorAsync( } [Fact] - public Task HEADERS_Received_HeaderBlockOverLimit_ConnectionError() + public async Task HEADERS_Received_HeaderBlockOverLimit_431() { // > 32kb var headers = new[] @@ -2318,11 +2318,50 @@ public Task HEADERS_Received_HeaderBlockOverLimit_ConnectionError() new KeyValuePair("h", _4kHeaderValue), }; + var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_notImplementedApp, headers, endStream: true); + + var receivedHeaders = await requestStream.ExpectHeadersAsync(); + + await requestStream.ExpectReceiveEndOfStream(); + + Assert.Equal(3, receivedHeaders.Count); + Assert.Contains("date", receivedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("431", receivedHeaders[InternalHeaderNames.Status]); + Assert.Equal("0", receivedHeaders[HeaderNames.ContentLength]); + } + + [Fact] + public Task HEADERS_Received_HeaderBlockOverLimitx2_ConnectionError() + { + // > 32kb * 2 to exceed graceful handling limit + var headers = new[] + { + new KeyValuePair(InternalHeaderNames.Method, "GET"), + new KeyValuePair(InternalHeaderNames.Path, "/"), + new KeyValuePair(InternalHeaderNames.Scheme, "http"), + new KeyValuePair("a", _4kHeaderValue), + new KeyValuePair("b", _4kHeaderValue), + new KeyValuePair("c", _4kHeaderValue), + new KeyValuePair("d", _4kHeaderValue), + new KeyValuePair("e", _4kHeaderValue), + new KeyValuePair("f", _4kHeaderValue), + new KeyValuePair("g", _4kHeaderValue), + new KeyValuePair("h", _4kHeaderValue), + new KeyValuePair("i", _4kHeaderValue), + new KeyValuePair("j", _4kHeaderValue), + new KeyValuePair("k", _4kHeaderValue), + new KeyValuePair("l", _4kHeaderValue), + new KeyValuePair("m", _4kHeaderValue), + new KeyValuePair("n", _4kHeaderValue), + new KeyValuePair("o", _4kHeaderValue), + new KeyValuePair("p", _4kHeaderValue), + }; + return HEADERS_Received_InvalidHeaderFields_StreamError(headers, CoreStrings.BadRequest_HeadersExceedMaxTotalSize, Http3ErrorCode.RequestRejected); } [Fact] - public Task HEADERS_Received_TooManyHeaders_ConnectionError() + public async Task HEADERS_Received_TooManyHeaders_431() { // > MaxRequestHeaderCount (100) var headers = new List>(); @@ -2337,6 +2376,34 @@ public Task HEADERS_Received_TooManyHeaders_ConnectionError() headers.Add(new KeyValuePair(i.ToString(CultureInfo.InvariantCulture), i.ToString(CultureInfo.InvariantCulture))); } + var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_notImplementedApp, headers, endStream: true); + + var receivedHeaders = await requestStream.ExpectHeadersAsync(); + + await requestStream.ExpectReceiveEndOfStream(); + + Assert.Equal(3, receivedHeaders.Count); + Assert.Contains("date", receivedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("431", receivedHeaders[InternalHeaderNames.Status]); + Assert.Equal("0", receivedHeaders[HeaderNames.ContentLength]); + } + + [Fact] + public Task HEADERS_Received_TooManyHeadersx2_ConnectionError() + { + // > MaxRequestHeaderCount (100) * 2 to exceed graceful handling limit + var headers = new List>(); + headers.AddRange(new[] + { + new KeyValuePair(InternalHeaderNames.Method, "GET"), + new KeyValuePair(InternalHeaderNames.Path, "/"), + new KeyValuePair(InternalHeaderNames.Scheme, "http"), + }); + for (var i = 0; i < 200; i++) + { + headers.Add(new KeyValuePair(i.ToString(CultureInfo.InvariantCulture), i.ToString(CultureInfo.InvariantCulture))); + } + return HEADERS_Received_InvalidHeaderFields_StreamError(headers, CoreStrings.BadRequest_TooManyHeaders); } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index dc28eafb46d9..b5d0f612afd5 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -49,6 +49,7 @@ public abstract class Http3TestBase : TestApplicationErrorLoggerLoggedTest, IDis internal readonly Mock _mockTimeoutHandler = new Mock(); protected readonly RequestDelegate _noopApplication; + protected readonly RequestDelegate _notImplementedApp; protected readonly RequestDelegate _echoApplication; protected readonly RequestDelegate _readRateApplication; protected readonly RequestDelegate _echoMethod; @@ -79,6 +80,7 @@ protected static IEnumerable> ReadRateRequestHeader public Http3TestBase() { _noopApplication = context => Task.CompletedTask; + _notImplementedApp = _ => throw new NotImplementedException(); _echoApplication = async context => { diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs index 086a96b16356..6ab377f47815 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs @@ -1369,10 +1369,10 @@ public async Task Settings_MaxHeaderListSize_Server(string scheme) { request.Headers.Add("header" + i, oneKbString + i); } - // Kestrel closes the connection rather than sending the recommended 431 response. https://github.com/dotnet/aspnetcore/issues/17861 - await Assert.ThrowsAsync(() => client.SendAsync(request)).DefaultTimeout(); - + var response = await client.SendAsync(request).DefaultTimeout(); await host.StopAsync().DefaultTimeout(); + + Assert.Equal(HttpStatusCode.RequestHeaderFieldsTooLarge, response.StatusCode); } [Theory]