diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs index e36a7ae5ddfa6..3c33d451bc9ac 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs @@ -5,6 +5,7 @@ using System.Buffers.Binary; using System.Diagnostics; using System.IO.Pipelines; +using System.Net.Http; using System.Net.Http.HPack; using System.Threading.Channels; using Microsoft.AspNetCore.Connections; @@ -73,6 +74,8 @@ internal sealed class Http2FrameWriter private int _maxFrameSize = Http2PeerSettings.MinAllowedMaxFrameSize; private readonly ArrayBufferWriter _headerEncodingBuffer; + private readonly int? _maxResponseHeadersTotalSize; + private int _currentResponseHeadersTotalSize; private long _unflushedBytes; private bool _completed; @@ -110,7 +113,7 @@ public Http2FrameWriter( _headerEncodingBuffer = new ArrayBufferWriter(_maxFrameSize); _scheduleInline = serviceContext.Scheduler == PipeScheduler.Inline; - + _maxResponseHeadersTotalSize = _http2Connection.Limits.MaxResponseHeadersTotalSize; _hpackEncoder = new DynamicHPackEncoder(serviceContext.ServerOptions.AllowResponseHeaderCompression); _maximumFlowControlQueueSize = AppContextMaximumFlowControlQueueSize is null @@ -484,15 +487,21 @@ private void WriteResponseHeadersUnsynchronized(int streamId, int statusCode, Ht { try { + // In the case of the headers, there is always a status header to be returned, so BeginEncodeHeaders will not return BufferTooSmall. _headersEnumerator.Initialize(headers); _outgoingFrame.PrepareHeaders(headerFrameFlags, streamId); _headerEncodingBuffer.ResetWrittenCount(); var buffer = _headerEncodingBuffer.GetSpan(_maxFrameSize)[0.._maxFrameSize]; // GetSpan might return more data that can result in a less deterministic behavior on the way headers are split into frames. var done = HPackHeaderWriter.BeginEncodeHeaders(statusCode, _hpackEncoder, _headersEnumerator, buffer, out var payloadLength); Debug.Assert(done != HPackHeaderWriter.HeaderWriteResult.BufferTooSmall, "Oversized frames should not be returned, beucase this always writes the status."); + if (_maxResponseHeadersTotalSize.HasValue && payloadLength > _maxResponseHeadersTotalSize.Value) + { + ThrowResponseHeadersLimitException(); + } + _currentResponseHeadersTotalSize = payloadLength; if (done == HPackHeaderWriter.HeaderWriteResult.Done) { - // Fast path + // Fast path, only a single HEADER frame. _outgoingFrame.PayloadLength = payloadLength; _outgoingFrame.HeadersFlags |= Http2HeadersFrameFlags.END_HEADERS; WriteHeaderUnsynchronized(); @@ -500,9 +509,10 @@ private void WriteResponseHeadersUnsynchronized(int streamId, int statusCode, Ht } else { - // Slow path + // More headers sent in CONTINUATION frames. _headerEncodingBuffer.Advance(payloadLength); - FinishWritingHeaders(streamId, payloadLength, done); + SplitHeaderFramesToOutput(streamId, done, isFramePrepared: true); + FinishWritingHeaders(streamId); } } // Any exception from the HPack encoder can leave the dynamic table in a corrupt state. @@ -540,19 +550,46 @@ private ValueTask WriteDataAndTrailersAsync(Http2Stream stream, in try { + // In the case of the trailers, there is no status header to be written, so even the first call to BeginEncodeHeaders can return BufferTooSmall. _outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.END_STREAM, streamId); - var done = HPackHeaderWriter.HeaderWriteResult.MoreHeaders; - int payloadLength; + var bufferSize = _headerEncodingBuffer.Capacity; + HPackHeaderWriter.HeaderWriteResult done; do { _headersEnumerator.Initialize(headers); _headerEncodingBuffer.ResetWrittenCount(); - var bufferSize = done == HPackHeaderWriter.HeaderWriteResult.BufferTooSmall ? _headerEncodingBuffer.Capacity * 2 : _headerEncodingBuffer.Capacity; var buffer = _headerEncodingBuffer.GetSpan(bufferSize)[0..bufferSize]; // GetSpan might return more data that can result in a less deterministic behavior on the way headers are split into frames. - done = HPackHeaderWriter.BeginEncodeHeaders(_hpackEncoder, _headersEnumerator, buffer, out payloadLength); + done = HPackHeaderWriter.BeginEncodeHeaders(_hpackEncoder, _headersEnumerator, buffer, out var payloadLength); + if (done == HPackHeaderWriter.HeaderWriteResult.Done) + { + if (_maxResponseHeadersTotalSize.HasValue && _currentResponseHeadersTotalSize + payloadLength > _maxResponseHeadersTotalSize.Value) + { + ThrowResponseHeadersLimitException(); + } + _headerEncodingBuffer.Advance(payloadLength); + SplitHeaderFramesToOutput(streamId, done, isFramePrepared: true); + } + else if (done == HPackHeaderWriter.HeaderWriteResult.MoreHeaders) + { + // More headers sent in CONTINUATION frames. + _currentResponseHeadersTotalSize += payloadLength; + if (_maxResponseHeadersTotalSize.HasValue && _currentResponseHeadersTotalSize > _maxResponseHeadersTotalSize.Value) + { + ThrowResponseHeadersLimitException(); + } + _headerEncodingBuffer.Advance(payloadLength); + SplitHeaderFramesToOutput(streamId, done, isFramePrepared: true); + FinishWritingHeaders(streamId); + } + else + { + if (_maxResponseHeadersTotalSize.HasValue && _currentResponseHeadersTotalSize + bufferSize > _maxResponseHeadersTotalSize.Value) + { + ThrowResponseHeadersLimitException(); + } + bufferSize *= 2; + } } while (done == HPackHeaderWriter.HeaderWriteResult.BufferTooSmall); - _headerEncodingBuffer.Advance(payloadLength); - FinishWritingHeaders(streamId, payloadLength, done); } // Any exception from the HPack encoder can leave the dynamic table in a corrupt state. // Since we allow custom header encoders we don't know what type of exceptions to expect. @@ -594,18 +631,36 @@ private void SplitHeaderFramesToOutput(int streamId, HPackHeaderWriter.HeaderWri } } - private void FinishWritingHeaders(int streamId, int payloadLength, HPackHeaderWriter.HeaderWriteResult done) + private void FinishWritingHeaders(int streamId) { - SplitHeaderFramesToOutput(streamId, done, isFramePrepared: true); - while (done != HPackHeaderWriter.HeaderWriteResult.Done) + HPackHeaderWriter.HeaderWriteResult done; + var bufferSize = _headerEncodingBuffer.Capacity; + do { _headerEncodingBuffer.ResetWrittenCount(); - var bufferSize = done == HPackHeaderWriter.HeaderWriteResult.BufferTooSmall ? _headerEncodingBuffer.Capacity * 2 : _headerEncodingBuffer.Capacity; var buffer = _headerEncodingBuffer.GetSpan(bufferSize)[0..bufferSize]; - done = HPackHeaderWriter.ContinueEncodeHeaders(_hpackEncoder, _headersEnumerator, buffer, out payloadLength); - _headerEncodingBuffer.Advance(payloadLength); - SplitHeaderFramesToOutput(streamId, done, isFramePrepared: false); - } + done = HPackHeaderWriter.ContinueEncodeHeaders(_hpackEncoder, _headersEnumerator, buffer, out var payloadLength); + + if (done == HPackHeaderWriter.HeaderWriteResult.BufferTooSmall) + { + if (_maxResponseHeadersTotalSize.HasValue && _currentResponseHeadersTotalSize + bufferSize > _maxResponseHeadersTotalSize.Value) + { + ThrowResponseHeadersLimitException(); + } + bufferSize *= 2; + } + else + { + // In case of Done or MoreHeaders: write to output. + _currentResponseHeadersTotalSize += payloadLength; + if (_maxResponseHeadersTotalSize.HasValue && _currentResponseHeadersTotalSize > _maxResponseHeadersTotalSize.Value) + { + ThrowResponseHeadersLimitException(); + } + _headerEncodingBuffer.Advance(payloadLength); + SplitHeaderFramesToOutput(streamId, done, isFramePrepared: false); + } + } while (done != HPackHeaderWriter.HeaderWriteResult.Done); } /* Padding is not implemented @@ -1031,4 +1086,6 @@ private void EnqueueWaitingForMoreConnectionWindow(Http2OutputProducer producer) _http2Connection.Abort(new ConnectionAbortedException("HTTP/2 connection exceeded the outgoing flow control maximum queue size.")); } } + + private void ThrowResponseHeadersLimitException() => throw new HPackEncodingException(SR.Format(SR.net_http_headers_exceeded_length, _maxResponseHeadersTotalSize!)); } diff --git a/src/Servers/Kestrel/Core/src/KestrelServerLimits.cs b/src/Servers/Kestrel/Core/src/KestrelServerLimits.cs index ef6a9ea3264ea..a09f214b45037 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerLimits.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerLimits.cs @@ -15,6 +15,9 @@ public class KestrelServerLimits // Matches the non-configurable default response buffer size for Kestrel in 1.0.0 private long? _maxResponseBufferSize = 64 * 1024; + // Matches the HttpClientHandler.MaxResponseHeadersLength's response header size. + private int? _maxResponseHeadersTotalSize = 64 * 1024; + // Matches the default client_max_body_size in nginx. // Also large enough that most requests should be under the limit. private long? _maxRequestBufferSize = 1024 * 1024; @@ -256,6 +259,27 @@ public long? MaxConcurrentUpgradedConnections } } + /// + /// Gets or sets the maximum size of the total response headers. When set to null, the response headers total size is unlimited. + /// Defaults to 65,536 bytes (64 KB). + /// + /// + /// When set to null, the size of the response buffer is unlimited. + /// When set to zero, no headers are allowed to be returned. + /// + public int? MaxResponseHeadersTotalSize + { + get => _maxResponseHeadersTotalSize; + set + { + if (value.HasValue && value.Value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.NonNegativeNumberOrNullRequired); + } + _maxResponseHeadersTotalSize = value; + } + } + internal void Serialize(Utf8JsonWriter writer) { writer.WriteString(nameof(KeepAliveTimeout), KeepAliveTimeout.ToString()); @@ -323,6 +347,16 @@ internal void Serialize(Utf8JsonWriter writer) writer.WriteString(nameof(MinResponseDataRate), MinResponseDataRate?.ToString()); writer.WriteString(nameof(RequestHeadersTimeout), RequestHeadersTimeout.ToString()); + writer.WritePropertyName(nameof(MaxResponseHeadersTotalSize)); + if (MaxResponseHeadersTotalSize is null) + { + writer.WriteNullValue(); + } + else + { + writer.WriteNumberValue(MaxResponseHeadersTotalSize.Value); + } + // HTTP2 writer.WritePropertyName(nameof(Http2)); writer.WriteStartObject(); diff --git a/src/Servers/Kestrel/Core/src/PublicAPI.Unshipped.txt b/src/Servers/Kestrel/Core/src/PublicAPI.Unshipped.txt index 7dc5c58110bfa..284fdbb55b331 100644 --- a/src/Servers/Kestrel/Core/src/PublicAPI.Unshipped.txt +++ b/src/Servers/Kestrel/Core/src/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerLimits.MaxResponseHeadersTotalSize.get -> int? +Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerLimits.MaxResponseHeadersTotalSize.set -> void diff --git a/src/Servers/Kestrel/Core/test/Http2/Http2FrameWriterTests.cs b/src/Servers/Kestrel/Core/test/Http2/Http2FrameWriterTests.cs index cafd8a98a0f26..2d5ed3d51f32d 100644 --- a/src/Servers/Kestrel/Core/test/Http2/Http2FrameWriterTests.cs +++ b/src/Servers/Kestrel/Core/test/Http2/Http2FrameWriterTests.cs @@ -12,6 +12,9 @@ using Microsoft.AspNetCore.InternalTesting; using Moq; using Xunit; +using Microsoft.AspNetCore.Http.Features; +using Castle.Core; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; @@ -56,7 +59,16 @@ public async Task WriteWindowUpdate_UnsetsReservedBit() private Http2FrameWriter CreateFrameWriter(Pipe pipe) { var serviceContext = TestContextFactory.CreateServiceContext(new KestrelServerOptions()); - return new Http2FrameWriter(pipe.Writer, null, null, 1, null, null, null, _dirtyMemoryPool, serviceContext); + var featureCollection = new FeatureCollection(); + featureCollection.Set(new TestConnectionMetricsContextFeature()); + var connectionContext = TestContextFactory.CreateHttpConnectionContext( + serviceContext: serviceContext, + connectionContext: null, + transport: new DuplexPipe(pipe.Reader, pipe.Writer), + connectionFeatures: featureCollection); + + var http2Connection = new Http2Connection(connectionContext); + return new Http2FrameWriter(pipe.Writer, null, http2Connection, 1, null, null, null, _dirtyMemoryPool, serviceContext); } [Fact] @@ -92,6 +104,11 @@ public async Task WriteHeader_UnsetsReservedBit() Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x00 }, payload.Skip(5).Take(4).ToArray()); } + + private sealed class TestConnectionMetricsContextFeature : IConnectionMetricsContextFeature + { + public ConnectionMetricsContext MetricsContext { get; } + } } public static class PipeReaderExtensions diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2FrameWriterBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2FrameWriterBenchmark.cs index 9fe40252d54f1..c9f32b9ddef2b 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2FrameWriterBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2FrameWriterBenchmark.cs @@ -11,6 +11,8 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; using Microsoft.AspNetCore.InternalTesting; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks; @@ -34,10 +36,19 @@ public void GlobalSetup() httpParser: new HttpParser(), dateHeaderValueManager: new DateHeaderValueManager(TimeProvider.System)); + var featureCollection = new FeatureCollection(); + featureCollection.Set(new TestConnectionMetricsContextFeature()); + var connectionContext = TestContextFactory.CreateHttpConnectionContext( + serviceContext: serviceContext, + connectionContext: null, + transport: new DuplexPipe(_pipe.Reader, _pipe.Writer), + connectionFeatures: featureCollection); + var http2Connection = new Http2Connection(connectionContext); + _frameWriter = new Http2FrameWriter( new NullPipeWriter(), connectionContext: null, - http2Connection: null, + http2Connection: http2Connection, maxStreamsPerConnection: 1, timeoutControl: null, minResponseDataRate: null, @@ -63,4 +74,9 @@ public void Dispose() _pipe.Writer.Complete(); _memoryPool?.Dispose(); } + + private sealed class TestConnectionMetricsContextFeature : IConnectionMetricsContextFeature + { + public ConnectionMetricsContext MetricsContext { get; } + } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index 95282dd8ffb5d..589fa6ced364a 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -5554,4 +5554,193 @@ await ExpectAsync(Http2FrameType.RST_STREAM, Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]); } + + [Fact] + public async Task Headers_LargerMaxResponseHeadersTotalSize_AbortsConnection() + { + _serviceContext.ServerOptions.Limits.MaxResponseHeadersTotalSize = 1; + await InitializeConnectionAsync(async context => + { + await context.Response.WriteAsync("Hello World"); + }); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + // Just the StatusCode gets written before aborting in the continuation frame + var goAway = await ExpectAsync(Http2FrameType.GOAWAY, + withLength: 8, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 0); + + VerifyGoAway(goAway, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); + + _pair.Application.Output.Complete(); + await _connectionTask; + } + + [Fact] + public async Task HeadersContinuation_LargerMaxResponseHeadersTotalSize_AbortsConnection() + { + _serviceContext.ServerOptions.Limits.MaxResponseHeadersTotalSize = (int)Http2PeerSettings.DefaultMaxFrameSize * 2; + await InitializeConnectionAsync(async context => + { + context.Response.Headers["My"] = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize); + context.Response.Headers["My2"] = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize); + await context.Response.WriteAsync("Hello World"); + }); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + // Just the StatusCode gets written before aborting in the continuation frame + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 32, + withFlags: (byte)Http2HeadersFrameFlags.NONE, + withStreamId: 1); + var headersFrame2 = await ExpectAsync(Http2FrameType.CONTINUATION, + withLength: 16384, + withFlags: (byte)Http2HeadersFrameFlags.NONE, + withStreamId: 1); + var headersFrame3 = await ExpectAsync(Http2FrameType.CONTINUATION, + withLength: 7, + withFlags: (byte)Http2HeadersFrameFlags.NONE, + withStreamId: 1); + + var goAway = await ExpectAsync(Http2FrameType.GOAWAY, + withLength: 8, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 0); + + VerifyGoAway(goAway, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); + + _pair.Application.Output.Complete(); + await _connectionTask; + } + + [Fact] + public async Task HeadersContinuation_BufferGrowsOverMaxResponseHeadersTotalSize_AbortsConnection() + { + _serviceContext.ServerOptions.Limits.MaxResponseHeadersTotalSize = (int)Http2PeerSettings.DefaultMaxFrameSize * 2; + await InitializeConnectionAsync(async context => + { + context.Response.Headers["My"] = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize * 2); + await context.Response.WriteAsync("Hello World"); + }); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + // Just the StatusCode gets written before aborting in the continuation frame + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 32, + withFlags: (byte)Http2HeadersFrameFlags.NONE, + withStreamId: 1); + + var goAway = await ExpectAsync(Http2FrameType.GOAWAY, + withLength: 8, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 0); + + VerifyGoAway(goAway, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); + + _pair.Application.Output.Complete(); + await _connectionTask; + } + + [Fact] + public async Task TrailersWhenDone_LargerMaxResponseHeadersTotalSize_AbortsConnection() + { + _serviceContext.ServerOptions.Limits.MaxResponseHeadersTotalSize = 100; + await InitializeConnectionAsync(async context => + { + await context.Response.WriteAsync("Hello World"); + context.Response.AppendTrailer("too_long", new string('a', 100)); + }); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + await ExpectAsync(Http2FrameType.HEADERS, + withLength: 32, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 11, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + // Just the StatusCode gets written before aborting in the continuation frame + var goAway = await ExpectAsync(Http2FrameType.GOAWAY, + withLength: 8, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 0); + + VerifyGoAway(goAway, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); + + _pair.Application.Output.Complete(); + await _connectionTask; + } + + [Fact] + public async Task TrailersWhenMoreHeader_LargerMaxResponseHeadersTotalSize_AbortsConnection() + { + _serviceContext.ServerOptions.Limits.MaxResponseHeadersTotalSize = 100; + await InitializeConnectionAsync(async context => + { + await context.Response.WriteAsync("Hello World"); + context.Response.AppendTrailer("My", new string('a', 100)); + context.Response.AppendTrailer("My2", new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize)); + }); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 32, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 11, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + var goAway = await ExpectAsync(Http2FrameType.GOAWAY, + withLength: 8, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 0); + + VerifyGoAway(goAway, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); + + _pair.Application.Output.Complete(); + await _connectionTask; + } + + [Fact] + public async Task TrailersWhenBufferTooSmall_LargerMaxResponseHeadersTotalSize_AbortsConnection() + { + _serviceContext.ServerOptions.Limits.MaxResponseHeadersTotalSize = 100; + await InitializeConnectionAsync(async context => + { + await context.Response.WriteAsync("Hello World"); + context.Response.AppendTrailer("My", new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize + 1)); + }); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 32, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + await ExpectAsync(Http2FrameType.DATA, + withLength: 11, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + var goAway = await ExpectAsync(Http2FrameType.GOAWAY, + withLength: 8, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 0); + + VerifyGoAway(goAway, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); + + _pair.Application.Output.Complete(); + await _connectionTask; + } } diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs index d9166a9d824c8..ad9b3ab585874 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs @@ -1381,7 +1381,18 @@ public async Task Settings_MaxHeaderListSize_Client(string scheme) var hostBuilder = new HostBuilder() .ConfigureWebHost(webHostBuilder => { - ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.UseKestrel(options => + { + options.Limits.MaxResponseHeadersTotalSize = 70_000; + options.Listen(IPAddress.Loopback, 0, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http2; + if (scheme == "https") + { + listenOptions.UseHttps(TestResources.GetTestCertificate()); + } + }); + }); webHostBuilder.ConfigureServices(AddTestLogging) .Configure(app => app.Run(context => {