diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 88aed68720cf..5f05596a101e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -1442,7 +1442,7 @@ private void OnHeaderCore(HeaderType headerType, int? staticTableIndex, ReadOnly { UpdateHeaderParsingState(value, GetPseudoHeaderField(staticTableIndex.GetValueOrDefault())); - _currentHeadersStream.OnHeader(staticTableIndex.GetValueOrDefault(), indexOnly: true, name, value); + _currentHeadersStream.OnHeader(staticTableIndex.GetValueOrDefault(), indexOnly: false, name, value); } else { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index 4b4569b261a5..35a272cfcc4e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -314,7 +314,7 @@ private void OnHeaderCore(HeaderType headerType, int? staticTableIndex, ReadOnly { UpdateHeaderParsingState(value, GetPseudoHeaderField(staticTableIndex.GetValueOrDefault())); - OnHeader(staticTableIndex.GetValueOrDefault(), indexOnly: true, name, value); + OnHeader(staticTableIndex.GetValueOrDefault(), indexOnly: false, name, value); } else { diff --git a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj index 530e06add285..ecbfa571142e 100644 --- a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj +++ b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj @@ -14,6 +14,8 @@ + + diff --git a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj index d8c23b7f1802..ccfe2c28c585 100644 --- a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj +++ b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj @@ -3,7 +3,7 @@ $(DefaultNetCoreTargetFramework) true Kestrel.Core.Tests - $(DefineConstants);KESTREL + $(DefineConstants);KESTREL;IS_TESTS @@ -11,6 +11,8 @@ + + diff --git a/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj b/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj index 961f0710b306..89bf8657724d 100644 --- a/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj +++ b/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj b/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj index 3ff7e48078f0..0b3f8eb86baf 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj +++ b/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj @@ -8,7 +8,12 @@ - + + + + + + diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs index ca70054c0352..8c44390a9aeb 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs @@ -22,6 +22,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; +using Http2HeadersEnumerator = Microsoft.AspNetCore.Server.Kestrel.Core.Tests.Http2HeadersEnumerator; namespace Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks; diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2HeadersEnumeratorBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2HeadersEnumeratorBenchmark.cs index 69a2e5d5b9e4..d1b90dd056e2 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2HeadersEnumeratorBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2HeadersEnumeratorBenchmark.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; using Microsoft.Extensions.Primitives; +using Http2HeadersEnumerator = Microsoft.AspNetCore.Server.Kestrel.Core.Tests.Http2HeadersEnumerator; namespace Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks; diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj b/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj index e2c374f63b38..073496464d85 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj @@ -6,6 +6,7 @@ true true false + $(DefineConstants);IS_BENCHMARKS @@ -15,6 +16,8 @@ + + diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs b/src/Servers/Kestrel/shared/HPackHeaderWriter.cs similarity index 95% rename from src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs rename to src/Servers/Kestrel/shared/HPackHeaderWriter.cs index cbed00bc6d97..22cf2256cd53 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs +++ b/src/Servers/Kestrel/shared/HPackHeaderWriter.cs @@ -4,8 +4,14 @@ using System.Net.Http; using System.Net.Http.HPack; +#if !(IS_TESTS || IS_BENCHMARKS) namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +#else +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; +#endif +// This file is used by Kestrel to write response headers and tests to write request headers. +// To avoid adding test code to Kestrel this file is shared. Test specifc code is excluded from Kestrel by ifdefs. internal static class HPackHeaderWriter { /// diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeadersEnumerator.cs b/src/Servers/Kestrel/shared/Http2HeadersEnumerator.cs similarity index 82% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeadersEnumerator.cs rename to src/Servers/Kestrel/shared/Http2HeadersEnumerator.cs index 2eaabd86d6dd..27688a1daae7 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeadersEnumerator.cs +++ b/src/Servers/Kestrel/shared/Http2HeadersEnumerator.cs @@ -7,20 +7,32 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.Extensions.Primitives; +#if !(IS_TESTS || IS_BENCHMARKS) namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +#else +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; +#endif +#nullable enable + +// This file is used by Kestrel to write response headers and tests to write request headers. +// To avoid adding test code to Kestrel this file is shared. Test specifc code is excluded from Kestrel by ifdefs. internal sealed class Http2HeadersEnumerator : IEnumerator> { private enum HeadersType : byte { Headers, Trailers, - Untyped +#if IS_TESTS || IS_BENCHMARKS + Untyped, +#endif } private HeadersType _headersType; private HttpResponseHeaders.Enumerator _headersEnumerator; private HttpResponseTrailers.Enumerator _trailersEnumerator; +#if IS_TESTS || IS_BENCHMARKS private IEnumerator>? _genericEnumerator; +#endif private StringValues.Enumerator _stringValuesEnumerator; private bool _hasMultipleValues; private KnownHeaderType _knownHeaderType; @@ -47,6 +59,7 @@ public void Initialize(HttpResponseTrailers headers) _hasMultipleValues = false; } +#if IS_TESTS || IS_BENCHMARKS public void Initialize(IDictionary headers) { switch (headers) @@ -67,6 +80,7 @@ public void Initialize(IDictionary headers) _hasMultipleValues = false; } +#endif public bool MoveNext() { @@ -89,12 +103,36 @@ public bool MoveNext() } else { +#if IS_TESTS || IS_BENCHMARKS return _genericEnumerator!.MoveNext() - ? SetCurrent(_genericEnumerator.Current.Key, _genericEnumerator.Current.Value, default) + ? SetCurrent(_genericEnumerator.Current.Key, _genericEnumerator.Current.Value, GetKnownRequestHeaderType(_genericEnumerator.Current.Key)) : false; +#else + ThrowUnexpectedHeadersType(); + return false; +#endif } } +#if IS_TESTS || IS_BENCHMARKS + private static KnownHeaderType GetKnownRequestHeaderType(string headerName) + { + switch (headerName) + { + case ":method": + return KnownHeaderType.Method; + default: + return default; + } + } +#else + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private static void ThrowUnexpectedHeadersType() + { + throw new InvalidOperationException("Unexpected headers collection type."); + } +#endif + private bool MoveNextOnStringEnumerator(string key) { var result = _stringValuesEnumerator.MoveNext(); @@ -134,7 +172,11 @@ public void Reset() } else { +#if IS_TESTS || IS_BENCHMARKS _genericEnumerator!.Reset(); +#else + ThrowUnexpectedHeadersType(); +#endif } _stringValuesEnumerator = default; _knownHeaderType = default; @@ -199,6 +241,11 @@ internal static int GetResponseHeaderStaticTableId(KnownHeaderType responseHeade return H2StaticTable.ContentLength; default: return -1; +#if IS_TESTS || IS_BENCHMARKS + // Include request headers for tests. + case KnownHeaderType.Method: + return H2StaticTable.MethodGet; +#endif } } } diff --git a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs index 41db3fd52e68..38300b3aa1fb 100644 --- a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs +++ b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs @@ -107,7 +107,7 @@ internal async ValueTask GetInboundControlStream() if (_inboundControlStream == null) { var reader = MultiplexedConnectionContext.ToClientAcceptQueue.Reader; -#if IS_FUNCTIONAL_TESTS +#if IS_TESTS while (await reader.WaitToReadAsync().DefaultTimeout()) #else while (await reader.WaitToReadAsync()) @@ -147,7 +147,7 @@ internal async Task WaitForConnectionErrorAsync(bool ignoreNonGoAway AssertConnectionError(expectedErrorCode, matchExpectedErrorMessage, expectedErrorMessage); // Verify HttpConnection.ProcessRequestsAsync has exited. -#if IS_FUNCTIONAL_TESTS +#if IS_TESTS await _connectionTask.DefaultTimeout(); #else await _connectionTask; @@ -461,7 +461,7 @@ protected Task SendAsync(ReadOnlySpan span) protected static Task FlushAsync(PipeWriter writableBuffer) { var task = writableBuffer.FlushAsync(); -#if IS_FUNCTIONAL_TESTS +#if IS_TESTS return task.AsTask().DefaultTimeout(); #else return task.GetAsTask(); @@ -477,7 +477,7 @@ internal async Task ReceiveEndAsync() } } -#if IS_FUNCTIONAL_TESTS +#if IS_TESTS protected Task ReadApplicationInputAsync() { return Pair.Application.Input.ReadAsync().AsTask().DefaultTimeout(); diff --git a/src/Servers/Kestrel/shared/test/PipeWriterHttp2FrameExtensions.cs b/src/Servers/Kestrel/shared/test/PipeWriterHttp2FrameExtensions.cs index c57f1b4b43b7..b78da63662a2 100644 --- a/src/Servers/Kestrel/shared/test/PipeWriterHttp2FrameExtensions.cs +++ b/src/Servers/Kestrel/shared/test/PipeWriterHttp2FrameExtensions.cs @@ -8,6 +8,8 @@ using System.IO.Pipelines; using System.Net.Http.HPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Http2HeadersEnumerator = Microsoft.AspNetCore.Server.Kestrel.Core.Tests.Http2HeadersEnumerator; +using HPackHeaderWriter = Microsoft.AspNetCore.Server.Kestrel.Core.Tests.HPackHeaderWriter; namespace Microsoft.AspNetCore.Testing; diff --git a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs index 119c90a28234..17eb640826d9 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs @@ -22,7 +22,6 @@ using Microsoft.AspNetCore.Server.Kestrel.FunctionalTests; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Tests; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index 2d50f1f32f86..fdeb2f7d47cf 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -103,22 +103,41 @@ public async Task HEADERS_Received_KnownOrCustomMethods_Accepted(string method) }; await InitializeConnectionAsync(_echoMethodNoBody); + // First request await StartStreamAsync(1, headers, endStream: true); - var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + var headersFrame1 = await ExpectAsync(Http2FrameType.HEADERS, withLength: 45 + method.Length, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); - await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + _hpackDecoder.Decode(headersFrame1.PayloadSequence, endHeaders: false, handler: this); - _hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this); + Assert.Equal(4, _decodedHeaders.Count); + Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); + Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]); + Assert.Equal(method, _decodedHeaders["Method"]); + _decodedHeaders.Clear(); + + // Second request (will use dynamic table indexes) + await StartStreamAsync(3, headers, endStream: true); + + var headersFrame2 = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 7, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), + withStreamId: 3); + + await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + + _hpackDecoder.Decode(headersFrame2.PayloadSequence, endHeaders: false, handler: this); Assert.Equal(4, _decodedHeaders.Count); Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]); Assert.Equal(method, _decodedHeaders["Method"]); + _decodedHeaders.Clear(); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj index 04bfcda57908..16dc2c48315b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj @@ -4,7 +4,7 @@ true InMemory.FunctionalTests true - $(DefineConstants);IS_FUNCTIONAL_TESTS + $(DefineConstants);IS_TESTS @@ -12,6 +12,8 @@ + + diff --git a/src/Servers/Kestrel/test/Sockets.BindTests/Sockets.BindTests.csproj b/src/Servers/Kestrel/test/Sockets.BindTests/Sockets.BindTests.csproj index 0f7576c452d9..4338b1ea87f8 100644 --- a/src/Servers/Kestrel/test/Sockets.BindTests/Sockets.BindTests.csproj +++ b/src/Servers/Kestrel/test/Sockets.BindTests/Sockets.BindTests.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) @@ -10,7 +10,16 @@ - + + + + + + + + + + diff --git a/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj b/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj index d42ac6b661f8..b128aa8aff69 100644 --- a/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj +++ b/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj @@ -11,7 +11,19 @@ - + + + + + + + + + + + + +