diff --git a/src/Servers/HttpSys/HttpSysServer.sln b/src/Servers/HttpSys/HttpSysServer.sln index bdd6b9c522..dddd3217b2 100644 --- a/src/Servers/HttpSys/HttpSysServer.sln +++ b/src/Servers/HttpSys/HttpSysServer.sln @@ -24,7 +24,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution version.xml = version.xml EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "samples\TestClient\TestClient.csproj", "{8B828433-B333-4C19-96AE-00BFFF9D8841}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestClient", "samples\TestClient\TestClient.csproj", "{8B828433-B333-4C19-96AE-00BFFF9D8841}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SelfHostServer", "samples\SelfHostServer\SelfHostServer.csproj", "{1236F93A-AC5C-4A77-9477-C88F040151CA}" EndProject @@ -72,6 +72,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Connec EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QueueSharing", "samples\QueueSharing\QueueSharing.csproj", "{9B58DF76-DC6D-4728-86B7-40087BDDC897}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets", "..\Kestrel\Transport.Sockets\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj", "{33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -318,6 +320,18 @@ Global {9B58DF76-DC6D-4728-86B7-40087BDDC897}.Release|Mixed Platforms.Build.0 = Release|Any CPU {9B58DF76-DC6D-4728-86B7-40087BDDC897}.Release|x86.ActiveCfg = Release|Any CPU {9B58DF76-DC6D-4728-86B7-40087BDDC897}.Release|x86.Build.0 = Release|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Debug|x86.ActiveCfg = Debug|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Debug|x86.Build.0 = Debug|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Release|Any CPU.Build.0 = Release|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Release|x86.ActiveCfg = Release|Any CPU + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -344,6 +358,7 @@ Global {D93575B3-BFA3-4523-B060-D268D6A0A66B} = {4DA3C456-5050-4AC0-A554-795F6DEC8660} {00A88B8D-D539-45DD-B071-1E955AF89A4A} = {4DA3C456-5050-4AC0-A554-795F6DEC8660} {9B58DF76-DC6D-4728-86B7-40087BDDC897} = {3A1E31E3-2794-4CA3-B8E2-253E96BDE514} + {33CF53ED-A4BC-4EAA-9EA7-EF5E748A03BB} = {4DA3C456-5050-4AC0-A554-795F6DEC8660} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {34B42B42-FA09-41AB-9216-14073990C504} diff --git a/src/Servers/HttpSys/test/FunctionalTests/Http2Tests.cs b/src/Servers/HttpSys/test/FunctionalTests/Http2Tests.cs new file mode 100644 index 0000000000..990d35a647 --- /dev/null +++ b/src/Servers/HttpSys/test/FunctionalTests/Http2Tests.cs @@ -0,0 +1,231 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http2Cat; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; +using Xunit; + +namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests +{ + public class Http2Tests + { + [ConditionalFact(Skip = "https://github.com/aspnet/AspNetCore/issues/17420")] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")] + [MaximumOSVersion(OperatingSystems.Windows, "10.0.18362.9999", SkipReason = "This is last version without GoAway support")] + public async Task ConnectionClose_NoOSSupport_NoGoAway() + { + using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + httpContext.Response.Headers[HeaderNames.Connection] = "close"; + return Task.FromResult(0); + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + var headersFrame = await h2Connection.ReceiveFrameAsync(); + + Assert.Equal(Http2FrameType.HEADERS, headersFrame.Type); + Assert.True((headersFrame.Flags & (byte)Http2HeadersFrameFlags.END_HEADERS) != 0); + Assert.True((headersFrame.Flags & (byte)Http2HeadersFrameFlags.END_STREAM) != 0); + + h2Connection.Logger.LogInformation("Received headers in a single frame."); + + var decodedHeaders = h2Connection.DecodeHeaders(headersFrame); + + // HTTP/2 filters out the connection header + Assert.False(decodedHeaders.ContainsKey(HeaderNames.Connection)); + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + + // Send and receive a second request to ensure there is no GoAway frame on the wire yet. + + await h2Connection.StartStreamAsync(3, Http2Utilities.BrowserRequestHeaders, endStream: true); + + headersFrame = await h2Connection.ReceiveFrameAsync(); + + Assert.Equal(Http2FrameType.HEADERS, headersFrame.Type); + Assert.True((headersFrame.Flags & (byte)Http2HeadersFrameFlags.END_HEADERS) != 0); + Assert.True((headersFrame.Flags & (byte)Http2HeadersFrameFlags.END_STREAM) != 0); + + h2Connection.Logger.LogInformation("Received headers in a single frame."); + + h2Connection.ResetHeaders(); + decodedHeaders = h2Connection.DecodeHeaders(headersFrame); + + // HTTP/2 filters out the connection header + Assert.False(decodedHeaders.ContainsKey(HeaderNames.Connection)); + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + + await h2Connection.StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_19H2, SkipReason = "GoAway support was added in Win10_19H2.")] + public async Task ConnectionClose_OSSupport_SendsGoAway() + { + using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + httpContext.Response.Headers[HeaderNames.Connection] = "close"; + return Task.FromResult(0); + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + var goAwayFrame = await h2Connection.ReceiveFrameAsync(); + h2Connection.VerifyGoAway(goAwayFrame, int.MaxValue, Http2ErrorCode.NO_ERROR); + + var headersFrame = await h2Connection.ReceiveFrameAsync(); + + Assert.Equal(Http2FrameType.HEADERS, headersFrame.Type); + Assert.Equal(Http2HeadersFrameFlags.END_HEADERS, headersFrame.HeadersFlags); + + h2Connection.Logger.LogInformation("Received headers in a single frame."); + + var decodedHeaders = h2Connection.DecodeHeaders(headersFrame); + + // HTTP/2 filters out the connection header + Assert.False(decodedHeaders.ContainsKey(HeaderNames.Connection)); + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + + var dataFrame = await h2Connection.ReceiveFrameAsync(); + Assert.Equal(Http2FrameType.DATA, dataFrame.Type); + Assert.Equal(Http2DataFrameFlags.END_STREAM, dataFrame.DataFlags); + Assert.Equal(0, dataFrame.PayloadLength); + + // Http.Sys doesn't send a final GoAway unless we ignore the first one and send 200 additional streams. + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_19H2, SkipReason = "GoAway support was added in Win10_19H2.")] + public async Task ConnectionClose_AdditionalRequests_ReceivesSecondGoAway() + { + using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + httpContext.Response.Headers[HeaderNames.Connection] = "close"; + return Task.FromResult(0); + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + var streamId = 1; + await h2Connection.StartStreamAsync(streamId, Http2Utilities.BrowserRequestHeaders, endStream: true); + + var goAwayFrame = await h2Connection.ReceiveFrameAsync(); + h2Connection.VerifyGoAway(goAwayFrame, int.MaxValue, Http2ErrorCode.NO_ERROR); + + var headersFrame = await h2Connection.ReceiveFrameAsync(); + + Assert.Equal(Http2FrameType.HEADERS, headersFrame.Type); + Assert.Equal(Http2HeadersFrameFlags.END_HEADERS, headersFrame.HeadersFlags); + Assert.Equal(streamId, headersFrame.StreamId); + + h2Connection.Logger.LogInformation("Received headers in a single frame."); + + var decodedHeaders = h2Connection.DecodeHeaders(headersFrame); + + // HTTP/2 filters out the connection header + Assert.False(decodedHeaders.ContainsKey(HeaderNames.Connection)); + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + h2Connection.ResetHeaders(); + + var dataFrame = await h2Connection.ReceiveFrameAsync(); + Assert.Equal(Http2FrameType.DATA, dataFrame.Type); + Assert.Equal(Http2DataFrameFlags.END_STREAM, dataFrame.DataFlags); + Assert.Equal(0, dataFrame.PayloadLength); + Assert.Equal(streamId, dataFrame.StreamId); + + // Http.Sys doesn't send a final GoAway unless we ignore the first one and send 200 additional streams. + + for (var i = 1; i < 200; i++) + { + streamId = 1 + (i * 2); // Odds. + await h2Connection.StartStreamAsync(streamId, Http2Utilities.BrowserRequestHeaders, endStream: true); + + headersFrame = await h2Connection.ReceiveFrameAsync(); + + Assert.Equal(Http2FrameType.HEADERS, headersFrame.Type); + Assert.Equal(Http2HeadersFrameFlags.END_HEADERS, headersFrame.HeadersFlags); + Assert.Equal(streamId, headersFrame.StreamId); + + h2Connection.Logger.LogInformation("Received headers in a single frame."); + + decodedHeaders = h2Connection.DecodeHeaders(headersFrame); + + // HTTP/2 filters out the connection header + Assert.False(decodedHeaders.ContainsKey(HeaderNames.Connection)); + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + h2Connection.ResetHeaders(); + + dataFrame = await h2Connection.ReceiveFrameAsync(); + Assert.Equal(Http2FrameType.DATA, dataFrame.Type); + Assert.Equal(Http2DataFrameFlags.END_STREAM, dataFrame.DataFlags); + Assert.Equal(0, dataFrame.PayloadLength); + Assert.Equal(streamId, dataFrame.StreamId); + } + + streamId = 1 + (200 * 2); // Odds. + await h2Connection.StartStreamAsync(streamId, Http2Utilities.BrowserRequestHeaders, endStream: true); + + // Final GoAway + goAwayFrame = await h2Connection.ReceiveFrameAsync(); + h2Connection.VerifyGoAway(goAwayFrame, streamId, Http2ErrorCode.NO_ERROR); + + // Normal response + headersFrame = await h2Connection.ReceiveFrameAsync(); + + Assert.Equal(Http2FrameType.HEADERS, headersFrame.Type); + Assert.Equal(Http2HeadersFrameFlags.END_HEADERS, headersFrame.HeadersFlags); + Assert.Equal(streamId, headersFrame.StreamId); + + h2Connection.Logger.LogInformation("Received headers in a single frame."); + + decodedHeaders = h2Connection.DecodeHeaders(headersFrame); + + // HTTP/2 filters out the connection header + Assert.False(decodedHeaders.ContainsKey(HeaderNames.Connection)); + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + h2Connection.ResetHeaders(); + + dataFrame = await h2Connection.ReceiveFrameAsync(); + Assert.Equal(Http2FrameType.DATA, dataFrame.Type); + Assert.Equal(Http2DataFrameFlags.END_STREAM, dataFrame.DataFlags); + Assert.Equal(0, dataFrame.PayloadLength); + Assert.Equal(streamId, dataFrame.StreamId); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + } +} diff --git a/src/Servers/HttpSys/test/FunctionalTests/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj b/src/Servers/HttpSys/test/FunctionalTests/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj index 281d455f5e..16991ba87a 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj +++ b/src/Servers/HttpSys/test/FunctionalTests/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) @@ -6,8 +6,17 @@ true + + + + + + + + + @@ -22,4 +31,15 @@ + + + Microsoft.AspNetCore.Server.SharedStrings + + + + System.Net.Http.SR + + + + 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 1c8ebd0712..fc50e5df73 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 @@ -15,7 +15,8 @@ - + + @@ -35,6 +36,10 @@ + + Microsoft.AspNetCore.Server.SharedStrings + + System.Net.Http.SR diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs index bda4eb3e25..a568f7a02c 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs @@ -291,19 +291,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal return new X509Certificate2(certificate); } - - private class SslDuplexPipe : DuplexPipeStreamAdapter - { - public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions) - : this(transport, readerOptions, writerOptions, s => new SslStream(s)) - { - - } - - public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func factory) : - base(transport, readerOptions, writerOptions, factory) - { - } - } } } diff --git a/src/Servers/Kestrel/Core/src/Properties/AssemblyInfo.cs b/src/Servers/Kestrel/Core/src/Properties/AssemblyInfo.cs index fa7073fd5f..706d5176fb 100644 --- a/src/Servers/Kestrel/Core/src/Properties/AssemblyInfo.cs +++ b/src/Servers/Kestrel/Core/src/Properties/AssemblyInfo.cs @@ -14,4 +14,3 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Performance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] -[assembly: InternalsVisibleTo("http2cat, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] 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 f64bf87991..209b20b11e 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 @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) @@ -13,7 +13,6 @@ - diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj b/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj index 3fc89b9e9f..dc7a76c818 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj +++ b/src/Servers/Kestrel/Transport.Libuv/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj b/src/Servers/Kestrel/Transport.MsQuic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj index c052a41f72..c777d59b89 100644 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj +++ b/src/Servers/Kestrel/Transport.MsQuic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp.cs b/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp.cs index 7add3dc701..a08dc70324 100644 --- a/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp.cs +++ b/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp.cs @@ -26,3 +26,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets public bool NoDelay { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Client +{ + public partial class SocketConnectionFactory : Microsoft.AspNetCore.Connections.IConnectionFactory, System.IAsyncDisposable + { + public SocketConnectionFactory(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public System.Threading.Tasks.ValueTask ConnectAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + } +} diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs b/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs index b52b697260..20d5b97018 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs @@ -15,7 +15,7 @@ using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Client { - internal class SocketConnectionFactory : IConnectionFactory, IAsyncDisposable + public class SocketConnectionFactory : IConnectionFactory, IAsyncDisposable { private readonly SocketTransportOptions _options; private readonly MemoryPool _memoryPool; diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj b/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj index b9b5bdc589..6bb6ff2ce3 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj +++ b/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj @@ -1,4 +1,4 @@ - + Managed socket transport for the ASP.NET Core Kestrel cross-platform web server. @@ -14,7 +14,7 @@ - + @@ -26,10 +26,6 @@ - - - - diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Microsoft.AspNetCore.Server.Kestrel.Performance.csproj b/src/Servers/Kestrel/perf/Kestrel.Performance/Microsoft.AspNetCore.Server.Kestrel.Performance.csproj index 61d180a73c..df7255d954 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/Microsoft.AspNetCore.Server.Kestrel.Performance.csproj +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Microsoft.AspNetCore.Server.Kestrel.Performance.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) @@ -16,7 +16,6 @@ - diff --git a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs index c6b37eb216..2b26fe09c6 100644 --- a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs @@ -28,13 +28,13 @@ namespace Http2SampleApp var basePort = context.Configuration.GetValue("BASE_PORT") ?? 5000; // Http/1.1 endpoint for comparison - options.Listen(IPAddress.Any, basePort, listenOptions => + options.ListenAnyIP(basePort, listenOptions => { listenOptions.Protocols = HttpProtocols.Http1; }); // TLS Http/1.1 or HTTP/2 endpoint negotiated via ALPN - options.Listen(IPAddress.Any, basePort + 1, listenOptions => + options.ListenAnyIP(basePort + 1, listenOptions => { listenOptions.Protocols = HttpProtocols.Http1AndHttp2; listenOptions.UseHttps(); @@ -56,7 +56,7 @@ namespace Http2SampleApp // Prior knowledge, no TLS handshake. WARNING: Not supported by browsers // but useful for the h2spec tests - options.Listen(IPAddress.Any, basePort + 5, listenOptions => + options.ListenAnyIP(basePort + 5, listenOptions => { listenOptions.Protocols = HttpProtocols.Http2; }); diff --git a/src/Servers/Kestrel/samples/http2cat/Program.cs b/src/Servers/Kestrel/samples/http2cat/Program.cs index 7b9f5d7033..f7a8634bda 100644 --- a/src/Servers/Kestrel/samples/http2cat/Program.cs +++ b/src/Servers/Kestrel/samples/http2cat/Program.cs @@ -1,161 +1,81 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.IO.Pipelines; -using System.Net; -using System.Net.Security; -using System.Security.Authentication; using System.Text; -using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Connections.Features; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Http2Cat; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Client; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace http2cat { - class Program + public class Program { - static async Task Main(string[] args) + public static async Task Main(string[] args) { - var host = new HostBuilder() + using var host = new HostBuilder() .ConfigureLogging(loggingBuilder => { loggingBuilder.AddConsole(); }) - .ConfigureServices(services => - { - services.AddSingleton(); - services.AddSingleton(); - }) + .UseHttp2Cat("https://localhost:5001", RunTestCase) .Build(); - await host.Services.GetService().RunAsync(); + await host.RunAsync(); } - private class Http2CatHostedService + internal static async Task RunTestCase(Http2Utilities h2Connection) { - private readonly IConnectionFactory _connectionFactory; - private readonly ILogger _logger; + await h2Connection.InitializeConnectionAsync(); - public Http2CatHostedService(IConnectionFactory connectionFactory, ILogger logger) + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + var headersFrame = await h2Connection.ReceiveFrameAsync(); + + Trace.Assert(headersFrame.Type == Http2FrameType.HEADERS, headersFrame.Type.ToString()); + Trace.Assert((headersFrame.Flags & (byte)Http2HeadersFrameFlags.END_HEADERS) != 0); + Trace.Assert((headersFrame.Flags & (byte)Http2HeadersFrameFlags.END_STREAM) == 0); + + h2Connection.Logger.LogInformation("Received headers in a single frame."); + + var decodedHeaders = h2Connection.DecodeHeaders(headersFrame); + + foreach (var header in decodedHeaders) { - _connectionFactory = connectionFactory; - _logger = logger; + h2Connection.Logger.LogInformation($"{header.Key}: {header.Value}"); } - public async Task RunAsync() + var dataFrame = await h2Connection.ReceiveFrameAsync(); + + Trace.Assert(dataFrame.Type == Http2FrameType.DATA); + Trace.Assert((dataFrame.Flags & (byte)Http2DataFrameFlags.END_STREAM) == 0); + + h2Connection.Logger.LogInformation("Received data in a single frame."); + + h2Connection.Logger.LogInformation(Encoding.UTF8.GetString(dataFrame.Payload.ToArray())); + + var trailersFrame = await h2Connection.ReceiveFrameAsync(); + + Trace.Assert(trailersFrame.Type == Http2FrameType.HEADERS); + Trace.Assert((trailersFrame.Flags & (byte)Http2DataFrameFlags.END_STREAM) == 1); + + h2Connection.Logger.LogInformation("Received trailers in a single frame."); + + h2Connection.ResetHeaders(); + var decodedTrailers = h2Connection.DecodeHeaders(trailersFrame); + + foreach (var header in decodedTrailers) { - var endpoint = new IPEndPoint(IPAddress.Loopback, 5001); - - _logger.LogInformation($"Connecting to '{endpoint}'."); - - await using var context = await _connectionFactory.ConnectAsync(endpoint); - - _logger.LogInformation($"Connected to '{endpoint}'. Starting TLS handshake."); - - var memoryPool = context.Features.Get()?.MemoryPool; - var inputPipeOptions = new StreamPipeReaderOptions(memoryPool, memoryPool.GetMinimumSegmentSize(), memoryPool.GetMinimumAllocSize(), leaveOpen: true); - var outputPipeOptions = new StreamPipeWriterOptions(pool: memoryPool, leaveOpen: true); - - await using var sslDuplexPipe = new SslDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions); - await using var sslStream = sslDuplexPipe.Stream; - - var originalTransport = context.Transport; - context.Transport = sslDuplexPipe; - - try - { - await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions - { - TargetHost = "localhost", - RemoteCertificateValidationCallback = (_, __, ___, ____) => true, - ApplicationProtocols = new List { SslApplicationProtocol.Http2 }, - EnabledSslProtocols = SslProtocols.Tls12, - }, CancellationToken.None); - - _logger.LogInformation($"TLS handshake completed successfully."); - - var http2Utilities = new Http2Utilities(context); - - await http2Utilities.InitializeConnectionAsync(); - - _logger.LogInformation("Initialized http2 connection. Starting stream 1."); - - await http2Utilities.StartStreamAsync(1, Http2Utilities._browserRequestHeaders, endStream: true); - - var headersFrame = await http2Utilities.ReceiveFrameAsync(); - - Trace.Assert(headersFrame.Type == Http2FrameType.HEADERS); - Trace.Assert((headersFrame.Flags & (byte)Http2HeadersFrameFlags.END_HEADERS) != 0); - Trace.Assert((headersFrame.Flags & (byte)Http2HeadersFrameFlags.END_STREAM) == 0); - - _logger.LogInformation("Received headers in a single frame."); - - var decodedHeaders = http2Utilities.DecodeHeaders(headersFrame); - - foreach (var header in decodedHeaders) - { - _logger.LogInformation($"{header.Key}: {header.Value}"); - } - - var dataFrame = await http2Utilities.ReceiveFrameAsync(); - - Trace.Assert(dataFrame.Type == Http2FrameType.DATA); - Trace.Assert((dataFrame.Flags & (byte)Http2DataFrameFlags.END_STREAM) == 0); - - _logger.LogInformation("Received data in a single frame."); - - _logger.LogInformation(Encoding.UTF8.GetString(dataFrame.Payload.ToArray())); - - var trailersFrame = await http2Utilities.ReceiveFrameAsync(); - - Trace.Assert(trailersFrame.Type == Http2FrameType.HEADERS); - Trace.Assert((trailersFrame.Flags & (byte)Http2DataFrameFlags.END_STREAM) == 1); - - _logger.LogInformation("Received trailers in a single frame."); - - http2Utilities._decodedHeaders.Clear(); - - var decodedTrailers = http2Utilities.DecodeHeaders(trailersFrame); - - foreach (var header in decodedHeaders) - { - _logger.LogInformation($"{header.Key}: {header.Value}"); - } - - await http2Utilities.StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - - _logger.LogInformation("Connection stopped."); - } - finally - { - context.Transport = originalTransport; - } - } - } - - private class SslDuplexPipe : DuplexPipeStreamAdapter - { - public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions) - : this(transport, readerOptions, writerOptions, s => new SslStream(s)) - { - + h2Connection.Logger.LogInformation($"{header.Key}: {header.Value}"); } - public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func factory) : - base(transport, readerOptions, writerOptions, factory) - { - } + await h2Connection.StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + h2Connection.Logger.LogInformation("Connection stopped."); } } } diff --git a/src/Servers/Kestrel/samples/http2cat/http2cat.csproj b/src/Servers/Kestrel/samples/http2cat/http2cat.csproj index 50e4f6ea2b..f668fcfcf0 100644 --- a/src/Servers/Kestrel/samples/http2cat/http2cat.csproj +++ b/src/Servers/Kestrel/samples/http2cat/http2cat.csproj @@ -1,15 +1,39 @@ - + Exe netcoreapp5.0 + true + + + + + + + + + + + Microsoft.AspNetCore.Server.SharedStrings + + + + System.Net.Http.SR + + + + + + + + \ No newline at end of file diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj index c985b7d77d..e77498d951 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) @@ -14,7 +14,6 @@ - diff --git a/src/Shared/Http2cat/Http2CatHostedService.cs b/src/Shared/Http2cat/Http2CatHostedService.cs new file mode 100644 index 0000000000..8d8beed4aa --- /dev/null +++ b/src/Shared/Http2cat/Http2CatHostedService.cs @@ -0,0 +1,129 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Security.Authentication; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Http2Cat +{ + internal class Http2CatHostedService : IHostedService + { + private readonly IConnectionFactory _connectionFactory; + private readonly ILogger _logger; + private readonly CancellationTokenSource _stopTokenSource = new CancellationTokenSource(); + private Task _backgroundTask; + + public Http2CatHostedService(IConnectionFactory connectionFactory, ILogger logger, + IOptions options, IHostApplicationLifetime hostApplicationLifetime) + { + _connectionFactory = connectionFactory; + _logger = logger; + HostApplicationLifetime = hostApplicationLifetime; + Options = options.Value; + } + + public IHostApplicationLifetime HostApplicationLifetime { get; } + private Http2CatOptions Options { get; } + + public Task StartAsync(CancellationToken cancellationToken) + { + _backgroundTask = RunAsync(); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _stopTokenSource.Cancel(); + return _backgroundTask; + } + + private async Task RunAsync() + { + try + { + var address = BindingAddress.Parse(Options.Url); + + if (!IPAddress.TryParse(address.Host, out var ip)) + { + ip = Dns.GetHostEntry(address.Host).AddressList.First(); + } + + var endpoint = new IPEndPoint(ip, address.Port); + + _logger.LogInformation($"Connecting to '{endpoint}'."); + + await using var context = await _connectionFactory.ConnectAsync(endpoint); + + _logger.LogInformation($"Connected to '{endpoint}'."); + + var originalTransport = context.Transport; + IAsyncDisposable sslState = null; + if (address.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogInformation("Starting TLS handshake."); + + var memoryPool = context.Features.Get()?.MemoryPool; + var inputPipeOptions = new StreamPipeReaderOptions(memoryPool, memoryPool.GetMinimumSegmentSize(), memoryPool.GetMinimumAllocSize(), leaveOpen: true); + var outputPipeOptions = new StreamPipeWriterOptions(pool: memoryPool, leaveOpen: true); + + var sslDuplexPipe = new SslDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions); + var sslStream = sslDuplexPipe.Stream; + sslState = sslDuplexPipe; + + context.Transport = sslDuplexPipe; + + await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions + { + TargetHost = address.Host, + RemoteCertificateValidationCallback = (_, __, ___, ____) => true, + ApplicationProtocols = new List { SslApplicationProtocol.Http2 }, + EnabledSslProtocols = SslProtocols.Tls12, + }, CancellationToken.None); + + _logger.LogInformation($"TLS handshake completed successfully."); + } + + var http2Utilities = new Http2Utilities(context, _logger, _stopTokenSource.Token); + + try + { + await Options.Scenaro(http2Utilities); + } + catch (Exception ex) + { + _logger.LogError(ex, "App error"); + throw; + } + finally + { + // Unwind Https for shutdown. This must happen before context goes ot of scope or else DisposeAsync will hang + context.Transport = originalTransport; + + if (sslState != null) + { + await sslState.DisposeAsync(); + } + } + } + finally + { + HostApplicationLifetime.StopApplication(); + } + } + } +} diff --git a/src/Shared/Http2cat/Http2CatIHostBuilderExtensions.cs b/src/Shared/Http2cat/Http2CatIHostBuilderExtensions.cs new file mode 100644 index 0000000000..9a37aafffa --- /dev/null +++ b/src/Shared/Http2cat/Http2CatIHostBuilderExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http2Cat; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.Extensions.Hosting +{ + internal static class Http2CatIHostBuilderExtensions + { + public static IHostBuilder UseHttp2Cat(this IHostBuilder hostBuilder, string address, Func scenario) + { + hostBuilder.ConfigureServices(services => + { + services.UseHttp2Cat(options => + { + options.Url = address; + options.Scenaro = scenario; + }); + }); + return hostBuilder; + } + } +} diff --git a/src/Shared/Http2cat/Http2CatIServiceCollectionExtensions.cs b/src/Shared/Http2cat/Http2CatIServiceCollectionExtensions.cs new file mode 100644 index 0000000000..52c6449225 --- /dev/null +++ b/src/Shared/Http2cat/Http2CatIServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http2Cat; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Client; + +namespace Microsoft.Extensions.DependencyInjection +{ + internal static class Http2CatIServiceCollectionExtensions + { + public static IServiceCollection UseHttp2Cat(this IServiceCollection services, Action configureOptions) + { + services.AddSingleton(); + services.AddHostedService(); + services.Configure(configureOptions); + return services; + } + } +} diff --git a/src/Shared/Http2cat/Http2CatOptions.cs b/src/Shared/Http2cat/Http2CatOptions.cs new file mode 100644 index 0000000000..81e2b082f2 --- /dev/null +++ b/src/Shared/Http2cat/Http2CatOptions.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Http2Cat +{ + internal class Http2CatOptions + { + public string Url { get; set; } + public Func Scenaro { get; set; } + } +} diff --git a/src/Shared/Http2cat/Http2CatReadMe.md b/src/Shared/Http2cat/Http2CatReadMe.md new file mode 100644 index 0000000000..1bd01c17ec --- /dev/null +++ b/src/Shared/Http2cat/Http2CatReadMe.md @@ -0,0 +1,7 @@ +## Http2Cat + +Http2Cat is a low level Http2 testing framework designed to excersize a server with frame level control. This can be useful for unit testing and compat testing. + +The framework is distributed as internal sources since it shares the basic building blocks from Kestrel's Http2 implementation (frames, enum flags, frame reading and writing, etc.). InternalsVisibleTo should not be used, any needed components should be moved to one of the shared code directories. + +This Http2Cat folder contains non-production code used in the test client. The shared production code is kept in separate folders. diff --git a/src/Servers/Kestrel/samples/http2cat/Http2Utilities.cs b/src/Shared/Http2cat/Http2Utilities.cs similarity index 80% rename from src/Servers/Kestrel/samples/http2cat/Http2Utilities.cs rename to src/Shared/Http2cat/Http2Utilities.cs index 5129df13de..eace29386f 100644 --- a/src/Servers/Kestrel/samples/http2cat/Http2Utilities.cs +++ b/src/Shared/Http2cat/Http2Utilities.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.IO.Pipelines; @@ -12,26 +11,42 @@ using System.Linq; using System.Net.Http; using System.Net.Http.HPack; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; -namespace http2cat +namespace Microsoft.AspNetCore.Http2Cat { - public class Http2Utilities : IHttpHeadersHandler + internal class Http2Utilities : IHttpHeadersHandler { + public static ReadOnlySpan ClientPreface => new byte[24] { (byte)'P', (byte)'R', (byte)'I', (byte)' ', (byte)'*', (byte)' ', (byte)'H', (byte)'T', (byte)'T', (byte)'P', (byte)'/', (byte)'2', (byte)'.', (byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n', (byte)'S', (byte)'M', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' }; public static readonly int MaxRequestHeaderFieldSize = 16 * 1024; - public static readonly string _4kHeaderValue = new string('a', 4096); + public static readonly string FourKHeaderValue = new string('a', 4096); + private static readonly Encoding HeaderValueEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); - public static readonly IEnumerable> _browserRequestHeaders = new[] + public static readonly IEnumerable> BrowserRequestHeaders = new[] { new KeyValuePair(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Scheme, "https"), + new KeyValuePair(HeaderNames.Authority, "localhost:443"), + new KeyValuePair("user-agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"), + new KeyValuePair("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), + new KeyValuePair("accept-language", "en-US,en;q=0.5"), + new KeyValuePair("accept-encoding", "gzip, deflate, br"), + new KeyValuePair("upgrade-insecure-requests", "1"), + }; + + public static readonly IEnumerable> BrowserRequestHeadersHttp = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), new KeyValuePair(HeaderNames.Authority, "localhost:80"), new KeyValuePair("user-agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"), new KeyValuePair("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), @@ -40,7 +55,7 @@ namespace http2cat new KeyValuePair("upgrade-insecure-requests", "1"), }; - public static readonly IEnumerable> _postRequestHeaders = new[] + public static readonly IEnumerable> PostRequestHeaders = new[] { new KeyValuePair(HeaderNames.Method, "POST"), new KeyValuePair(HeaderNames.Path, "/"), @@ -48,7 +63,7 @@ namespace http2cat new KeyValuePair(HeaderNames.Authority, "localhost:80"), }; - public static readonly IEnumerable> _expectContinueRequestHeaders = new[] + public static readonly IEnumerable> ExpectContinueRequestHeaders = new[] { new KeyValuePair(HeaderNames.Method, "POST"), new KeyValuePair(HeaderNames.Path, "/"), @@ -57,37 +72,37 @@ namespace http2cat new KeyValuePair("expect", "100-continue"), }; - public static readonly IEnumerable> _requestTrailers = new[] + public static readonly IEnumerable> RequestTrailers = new[] { new KeyValuePair("trailer-one", "1"), new KeyValuePair("trailer-two", "2"), }; - public static readonly IEnumerable> _oneContinuationRequestHeaders = new[] + public static readonly IEnumerable> OneContinuationRequestHeaders = new[] { new KeyValuePair(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Scheme, "https"), new KeyValuePair(HeaderNames.Authority, "localhost:80"), - new KeyValuePair("a", _4kHeaderValue), - new KeyValuePair("b", _4kHeaderValue), - new KeyValuePair("c", _4kHeaderValue), - new KeyValuePair("d", _4kHeaderValue) + new KeyValuePair("a", FourKHeaderValue), + new KeyValuePair("b", FourKHeaderValue), + new KeyValuePair("c", FourKHeaderValue), + new KeyValuePair("d", FourKHeaderValue) }; - public static readonly IEnumerable> _twoContinuationsRequestHeaders = new[] + public static readonly IEnumerable> TwoContinuationsRequestHeaders = new[] { new KeyValuePair(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Scheme, "https"), new KeyValuePair(HeaderNames.Authority, "localhost:80"), - 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("a", FourKHeaderValue), + new KeyValuePair("b", FourKHeaderValue), + new KeyValuePair("c", FourKHeaderValue), + new KeyValuePair("d", FourKHeaderValue), + new KeyValuePair("e", FourKHeaderValue), + new KeyValuePair("f", FourKHeaderValue), + new KeyValuePair("g", FourKHeaderValue), }; public static IEnumerable> ReadRateRequestHeaders(int expectedBytes) => new[] @@ -114,15 +129,77 @@ namespace http2cat internal DuplexPipe.DuplexPipePair _pair; public long _bytesReceived; - public Http2Utilities(ConnectionContext clientConnectionContext) + public Http2Utilities(ConnectionContext clientConnectionContext, ILogger logger, CancellationToken stopToken) { _hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize, MaxRequestHeaderFieldSize); _pair = new DuplexPipe.DuplexPipePair(transport: null, application: clientConnectionContext.Transport); + Logger = logger; + StopToken = stopToken; } + public ILogger Logger { get; } + public CancellationToken StopToken { get; } + void IHttpHeadersHandler.OnHeader(ReadOnlySpan name, ReadOnlySpan value) { - _decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetAsciiOrUTF8StringNonNullCharacters(); + _decodedHeaders[GetAsciiStringNonNullCharacters(name)] = GetAsciiOrUTF8StringNonNullCharacters(value); + } + + public unsafe string GetAsciiStringNonNullCharacters(ReadOnlySpan span) + { + if (span.IsEmpty) + { + return string.Empty; + } + + var asciiString = new string('\0', span.Length); + + fixed (char* output = asciiString) + fixed (byte* buffer = span) + { + // This version if AsciiUtilities returns null if there are any null (0 byte) characters + // in the string + if (!StringUtilities.TryGetAsciiString(buffer, output, span.Length)) + { + throw new InvalidOperationException(); + } + } + return asciiString; + } + + public unsafe string GetAsciiOrUTF8StringNonNullCharacters(ReadOnlySpan span) + { + if (span.IsEmpty) + { + return string.Empty; + } + + var resultString = new string('\0', span.Length); + + fixed (char* output = resultString) + fixed (byte* buffer = span) + { + // This version if AsciiUtilities returns null if there are any null (0 byte) characters + // in the string + if (!StringUtilities.TryGetAsciiString(buffer, output, span.Length)) + { + // null characters are considered invalid + if (span.IndexOf((byte)0) != -1) + { + throw new InvalidOperationException(); + } + + try + { + resultString = HeaderValueEncoding.GetString(buffer, span.Length); + } + catch (DecoderFallbackException) + { + throw new InvalidOperationException(); + } + } + } + return resultString; } void IHttpHeadersHandler.OnHeadersComplete(bool endStream) { } @@ -170,7 +247,7 @@ namespace http2cat frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; } - Http2FrameWriter.WriteHeader(frame, writableBuffer); + WriteHeader(frame, writableBuffer); writableBuffer.Write(buffer.Slice(0, length)); while (!done) @@ -185,7 +262,7 @@ namespace http2cat frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS; } - Http2FrameWriter.WriteHeader(frame, writableBuffer); + WriteHeader(frame, writableBuffer); writableBuffer.Write(buffer.Slice(0, length)); } @@ -199,6 +276,38 @@ namespace http2cat return _decodedHeaders; } + internal void ResetHeaders() + { + _decodedHeaders.Clear(); + } + + /* https://tools.ietf.org/html/rfc7540#section-4.1 + +-----------------------------------------------+ + | Length (24) | + +---------------+---------------+---------------+ + | Type (8) | Flags (8) | + +-+-------------+---------------+-------------------------------+ + |R| Stream Identifier (31) | + +=+=============================================================+ + | Frame Payload (0...) ... + +---------------------------------------------------------------+ + */ + internal static void WriteHeader(Http2Frame frame, PipeWriter output) + { + var buffer = output.GetSpan(Http2FrameReader.HeaderLength); + + Bitshifter.WriteUInt24BigEndian(buffer, (uint)frame.PayloadLength); + buffer = buffer.Slice(3); + + buffer[0] = (byte)frame.Type; + buffer[1] = frame.Flags; + buffer = buffer.Slice(2); + + Bitshifter.WriteUInt31BigEndian(buffer, (uint)frame.StreamId, preserveHighestBit: false); + + output.Advance(Http2FrameReader.HeaderLength); + } + /* https://tools.ietf.org/html/rfc7540#section-6.2 +---------------+ |Pad Length? (8)| @@ -235,7 +344,7 @@ namespace http2cat frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; } - Http2FrameWriter.WriteHeader(frame, writableBuffer); + WriteHeader(frame, writableBuffer); writableBuffer.Write(buffer.Slice(0, frame.PayloadLength)); return FlushAsync(writableBuffer); } @@ -275,7 +384,7 @@ namespace http2cat frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; } - Http2FrameWriter.WriteHeader(frame, writableBuffer); + WriteHeader(frame, writableBuffer); writableBuffer.Write(buffer.Slice(0, frame.PayloadLength)); return FlushAsync(writableBuffer); } @@ -323,7 +432,7 @@ namespace http2cat frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; } - Http2FrameWriter.WriteHeader(frame, writableBuffer); + WriteHeader(frame, writableBuffer); writableBuffer.Write(buffer.Slice(0, frame.PayloadLength)); return FlushAsync(writableBuffer); } @@ -340,7 +449,7 @@ namespace http2cat await writableBuffer.FlushAsync().AsTask().DefaultTimeout(); } - public Task SendPreambleAsync() => SendAsync(Http2Connection.ClientPreface); + public Task SendPreambleAsync() => SendAsync(ClientPreface); public async Task SendSettingsAsync() { @@ -350,18 +459,28 @@ namespace http2cat var settings = _clientSettings.GetNonProtocolDefaults(); var payload = new byte[settings.Count * Http2FrameReader.SettingSize]; frame.PayloadLength = payload.Length; - Http2FrameWriter.WriteSettings(settings, payload); - Http2FrameWriter.WriteHeader(frame, writableBuffer); + WriteSettings(settings, payload); + WriteHeader(frame, writableBuffer); await SendAsync(payload); } + internal static void WriteSettings(IList settings, Span destination) + { + foreach (var setting in settings) + { + BinaryPrimitives.WriteUInt16BigEndian(destination, (ushort)setting.Parameter); + BinaryPrimitives.WriteUInt32BigEndian(destination.Slice(2), setting.Value); + destination = destination.Slice(Http2FrameReader.SettingSize); + } + } + public async Task SendSettingsAckWithInvalidLengthAsync(int length) { var writableBuffer = _pair.Application.Output; var frame = new Http2Frame(); frame.PrepareSettings(Http2SettingsFrameFlags.ACK); frame.PayloadLength = length; - Http2FrameWriter.WriteHeader(frame, writableBuffer); + WriteHeader(frame, writableBuffer); await SendAsync(new byte[length]); } @@ -374,8 +493,8 @@ namespace http2cat var settings = _clientSettings.GetNonProtocolDefaults(); var payload = new byte[settings.Count * Http2FrameReader.SettingSize]; frame.PayloadLength = payload.Length; - Http2FrameWriter.WriteSettings(settings, payload); - Http2FrameWriter.WriteHeader(frame, writableBuffer); + WriteSettings(settings, payload); + WriteHeader(frame, writableBuffer); await SendAsync(payload); } @@ -387,7 +506,7 @@ namespace http2cat frame.PayloadLength = length; var payload = new byte[length]; - Http2FrameWriter.WriteHeader(frame, writableBuffer); + WriteHeader(frame, writableBuffer); await SendAsync(payload); } @@ -405,7 +524,7 @@ namespace http2cat payload[4] = (byte)(value >> 8); payload[5] = (byte)value; - Http2FrameWriter.WriteHeader(frame, writableBuffer); + WriteHeader(frame, writableBuffer); await SendAsync(payload); } @@ -417,7 +536,7 @@ namespace http2cat frame.Type = Http2FrameType.PUSH_PROMISE; frame.StreamId = 1; - Http2FrameWriter.WriteHeader(frame, writableBuffer); + WriteHeader(frame, writableBuffer); return FlushAsync(writableBuffer); } @@ -431,7 +550,7 @@ namespace http2cat var done = _hpackEncoder.BeginEncode(headers, buffer.Span, out var length); frame.PayloadLength = length; - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); await SendAsync(buffer.Span.Slice(0, length)); return done; @@ -445,7 +564,7 @@ namespace http2cat frame.PrepareHeaders(flags, streamId); frame.PayloadLength = headerBlock.Length; - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); await SendAsync(headerBlock); } @@ -464,7 +583,7 @@ namespace http2cat payload[0] = padLength; } - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); await SendAsync(payload); } @@ -482,7 +601,7 @@ namespace http2cat payload[1] = 2; payload[2] = (byte)'a'; - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); await SendAsync(payload); } @@ -496,7 +615,7 @@ namespace http2cat var done = _hpackEncoder.Encode(buffer.Span, out var length); frame.PayloadLength = length; - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); await SendAsync(buffer.Span.Slice(0, length)); return done; @@ -510,7 +629,7 @@ namespace http2cat frame.PrepareContinuation(flags, streamId); frame.PayloadLength = payload.Length; - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); await SendAsync(payload); } @@ -524,7 +643,7 @@ namespace http2cat var done = _hpackEncoder.BeginEncode(headers, buffer.Span, out var length); frame.PayloadLength = length; - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); await SendAsync(buffer.Span.Slice(0, length)); return done; @@ -538,7 +657,7 @@ namespace http2cat frame.PrepareContinuation(flags, streamId); frame.PayloadLength = 0; - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); return FlushAsync(outputWriter); } @@ -556,7 +675,7 @@ namespace http2cat payload[1] = 2; payload[2] = (byte)'a'; - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); await SendAsync(payload); } @@ -569,7 +688,7 @@ namespace http2cat frame.PayloadLength = data.Length; frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE; - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); return SendAsync(data.Span); } @@ -586,7 +705,7 @@ namespace http2cat frame.DataFlags |= Http2DataFrameFlags.END_STREAM; } - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); outputWriter.GetSpan(1)[0] = padLength; outputWriter.Advance(1); await SendAsync(data.Span); @@ -609,7 +728,7 @@ namespace http2cat payload[0] = padLength; } - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); return SendAsync(payload); } @@ -618,7 +737,7 @@ namespace http2cat var outputWriter = _pair.Application.Output; var pingFrame = new Http2Frame(); pingFrame.PreparePing(flags); - Http2FrameWriter.WriteHeader(pingFrame, outputWriter); + WriteHeader(pingFrame, outputWriter); return SendAsync(new byte[8]); // Empty payload } @@ -628,7 +747,7 @@ namespace http2cat var pingFrame = new Http2Frame(); pingFrame.PreparePing(Http2PingFrameFlags.NONE); pingFrame.PayloadLength = length; - Http2FrameWriter.WriteHeader(pingFrame, outputWriter); + WriteHeader(pingFrame, outputWriter); return SendAsync(new byte[length]); } @@ -640,7 +759,7 @@ namespace http2cat var pingFrame = new Http2Frame(); pingFrame.PreparePing(Http2PingFrameFlags.NONE); pingFrame.StreamId = streamId; - Http2FrameWriter.WriteHeader(pingFrame, outputWriter); + WriteHeader(pingFrame, outputWriter); return SendAsync(new byte[pingFrame.PayloadLength]); } @@ -661,7 +780,7 @@ namespace http2cat Bitshifter.WriteUInt31BigEndian(payload, (uint)streamDependency); payload[4] = 0; // Weight - Http2FrameWriter.WriteHeader(priorityFrame, outputWriter); + WriteHeader(priorityFrame, outputWriter); return SendAsync(payload); } @@ -672,7 +791,7 @@ namespace http2cat priorityFrame.PreparePriority(streamId, streamDependency: 0, exclusive: false, weight: 0); priorityFrame.PayloadLength = length; - Http2FrameWriter.WriteHeader(priorityFrame, outputWriter); + WriteHeader(priorityFrame, outputWriter); return SendAsync(new byte[length]); } @@ -689,7 +808,7 @@ namespace http2cat var payload = new byte[rstStreamFrame.PayloadLength]; BinaryPrimitives.WriteUInt32BigEndian(payload, (uint)Http2ErrorCode.CANCEL); - Http2FrameWriter.WriteHeader(rstStreamFrame, outputWriter); + WriteHeader(rstStreamFrame, outputWriter); return SendAsync(payload); } @@ -699,7 +818,7 @@ namespace http2cat var frame = new Http2Frame(); frame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL); frame.PayloadLength = length; - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); return SendAsync(new byte[length]); } @@ -708,7 +827,7 @@ namespace http2cat var outputWriter = _pair.Application.Output; var frame = new Http2Frame(); frame.PrepareGoAway(0, Http2ErrorCode.NO_ERROR); - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); return SendAsync(new byte[frame.PayloadLength]); } @@ -718,7 +837,7 @@ namespace http2cat var frame = new Http2Frame(); frame.PrepareGoAway(0, Http2ErrorCode.NO_ERROR); frame.StreamId = 1; - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); return SendAsync(new byte[frame.PayloadLength]); } @@ -727,7 +846,7 @@ namespace http2cat var outputWriter = _pair.Application.Output; var frame = new Http2Frame(); frame.PrepareWindowUpdate(streamId, sizeIncrement); - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); var buffer = outputWriter.GetSpan(4); BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)sizeIncrement); outputWriter.Advance(4); @@ -740,7 +859,7 @@ namespace http2cat var frame = new Http2Frame(); frame.PrepareWindowUpdate(streamId, sizeIncrement); frame.PayloadLength = length; - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); return SendAsync(new byte[length]); } @@ -751,7 +870,7 @@ namespace http2cat frame.StreamId = streamId; frame.Type = (Http2FrameType)frameType; frame.PayloadLength = 0; - Http2FrameWriter.WriteHeader(frame, outputWriter); + WriteHeader(frame, outputWriter); return FlushAsync(outputWriter); } diff --git a/src/Servers/Kestrel/samples/http2cat/TaskTimeoutExtensions.cs b/src/Shared/Http2cat/TaskTimeoutExtensions.cs similarity index 92% rename from src/Servers/Kestrel/samples/http2cat/TaskTimeoutExtensions.cs rename to src/Shared/Http2cat/TaskTimeoutExtensions.cs index 943673291e..a293c1c66b 100644 --- a/src/Servers/Kestrel/samples/http2cat/TaskTimeoutExtensions.cs +++ b/src/Shared/Http2cat/TaskTimeoutExtensions.cs @@ -6,7 +6,7 @@ using System.Runtime.CompilerServices; namespace System.Threading.Tasks { - public static class TaskTimeoutExtensions + internal static class TaskTimeoutExtensions { public static TimeSpan DefaultTimeoutTimeSpan { get; } = TimeSpan.FromSeconds(5); @@ -30,7 +30,7 @@ namespace System.Threading.Tasks return task.TimeoutAfter(DefaultTimeoutTimeSpan); } - public static async Task TimeoutAfter(this Task task, TimeSpan timeout, + private static async Task TimeoutAfter(this Task task, TimeSpan timeout, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { @@ -53,7 +53,7 @@ namespace System.Threading.Tasks } } - public static async Task TimeoutAfter(this Task task, TimeSpan timeout, + private static async Task TimeoutAfter(this Task task, TimeSpan timeout, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/BufferExtensions.cs b/src/Shared/ServerInfrastructure/BufferExtensions.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http/BufferExtensions.cs rename to src/Shared/ServerInfrastructure/BufferExtensions.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/BufferWriter.cs b/src/Shared/ServerInfrastructure/BufferWriter.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/BufferWriter.cs rename to src/Shared/ServerInfrastructure/BufferWriter.cs diff --git a/src/Servers/Kestrel/shared/DuplexPipe.cs b/src/Shared/ServerInfrastructure/DuplexPipe.cs similarity index 100% rename from src/Servers/Kestrel/shared/DuplexPipe.cs rename to src/Shared/ServerInfrastructure/DuplexPipe.cs diff --git a/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStream.cs b/src/Shared/ServerInfrastructure/DuplexPipeStream.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStream.cs rename to src/Shared/ServerInfrastructure/DuplexPipeStream.cs diff --git a/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs b/src/Shared/ServerInfrastructure/DuplexPipeStreamAdapter.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs rename to src/Shared/ServerInfrastructure/DuplexPipeStreamAdapter.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Bitshifter.cs b/src/Shared/ServerInfrastructure/Http2/Bitshifter.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Bitshifter.cs rename to src/Shared/ServerInfrastructure/Http2/Bitshifter.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2ConnectionErrorException.cs b/src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2ConnectionErrorException.cs rename to src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2ContinuationFrameFlags.cs b/src/Shared/ServerInfrastructure/Http2/Http2ContinuationFrameFlags.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2ContinuationFrameFlags.cs rename to src/Shared/ServerInfrastructure/Http2/Http2ContinuationFrameFlags.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2DataFrameFlags.cs b/src/Shared/ServerInfrastructure/Http2/Http2DataFrameFlags.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2DataFrameFlags.cs rename to src/Shared/ServerInfrastructure/Http2/Http2DataFrameFlags.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2ErrorCode.cs b/src/Shared/ServerInfrastructure/Http2/Http2ErrorCode.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2ErrorCode.cs rename to src/Shared/ServerInfrastructure/Http2/Http2ErrorCode.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Continuation.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.Continuation.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Continuation.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.Continuation.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Data.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.Data.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Data.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.Data.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.GoAway.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.GoAway.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.GoAway.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.GoAway.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Headers.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.Headers.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Headers.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.Headers.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Ping.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.Ping.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Ping.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.Ping.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Priority.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.Priority.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Priority.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.Priority.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.RstStream.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.RstStream.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.RstStream.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.RstStream.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Settings.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.Settings.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.Settings.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.Settings.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.WindowUpdate.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.WindowUpdate.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.WindowUpdate.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.WindowUpdate.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.cs b/src/Shared/ServerInfrastructure/Http2/Http2Frame.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2Frame.cs rename to src/Shared/ServerInfrastructure/Http2/Http2Frame.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameReader.cs b/src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs similarity index 96% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameReader.cs rename to src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs index ed4db88f0e..5762b4a671 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameReader.cs +++ b/src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs @@ -6,7 +6,6 @@ using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.Diagnostics; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { @@ -46,7 +45,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 var payloadLength = (int)Bitshifter.ReadUInt24BigEndian(header); if (payloadLength > maxFrameSize) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorFrameOverLimit(payloadLength, maxFrameSize), Http2ErrorCode.FRAME_SIZE_ERROR); + throw new Http2ConnectionErrorException(SharedStrings.FormatHttp2ErrorFrameOverLimit(payloadLength, maxFrameSize), Http2ErrorCode.FRAME_SIZE_ERROR); } // Make sure the whole frame is buffered @@ -78,7 +77,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 if (extendedHeaderLength > frame.PayloadLength) { throw new Http2ConnectionErrorException( - CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(frame.Type, expectedLength: extendedHeaderLength), Http2ErrorCode.FRAME_SIZE_ERROR); + SharedStrings.FormatHttp2ErrorUnexpectedFrameLength(frame.Type, expectedLength: extendedHeaderLength), Http2ErrorCode.FRAME_SIZE_ERROR); } var extendedHeaders = readableBuffer.Slice(HeaderLength, extendedHeaderLength).ToSpan(); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameType.cs b/src/Shared/ServerInfrastructure/Http2/Http2FrameType.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameType.cs rename to src/Shared/ServerInfrastructure/Http2/Http2FrameType.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeadersFrameFlags.cs b/src/Shared/ServerInfrastructure/Http2/Http2HeadersFrameFlags.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2HeadersFrameFlags.cs rename to src/Shared/ServerInfrastructure/Http2/Http2HeadersFrameFlags.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2PeerSetting.cs b/src/Shared/ServerInfrastructure/Http2/Http2PeerSetting.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2PeerSetting.cs rename to src/Shared/ServerInfrastructure/Http2/Http2PeerSetting.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2PeerSettings.cs b/src/Shared/ServerInfrastructure/Http2/Http2PeerSettings.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2PeerSettings.cs rename to src/Shared/ServerInfrastructure/Http2/Http2PeerSettings.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2PingFrameFlags.cs b/src/Shared/ServerInfrastructure/Http2/Http2PingFrameFlags.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2PingFrameFlags.cs rename to src/Shared/ServerInfrastructure/Http2/Http2PingFrameFlags.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2SettingsFrameFlags.cs b/src/Shared/ServerInfrastructure/Http2/Http2SettingsFrameFlags.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2SettingsFrameFlags.cs rename to src/Shared/ServerInfrastructure/Http2/Http2SettingsFrameFlags.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2SettingsParameter.cs b/src/Shared/ServerInfrastructure/Http2/Http2SettingsParameter.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2SettingsParameter.cs rename to src/Shared/ServerInfrastructure/Http2/Http2SettingsParameter.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2SettingsParameterOutOfRangeException.cs b/src/Shared/ServerInfrastructure/Http2/Http2SettingsParameterOutOfRangeException.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Http2/Http2SettingsParameterOutOfRangeException.cs rename to src/Shared/ServerInfrastructure/Http2/Http2SettingsParameterOutOfRangeException.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/MemoryPoolExtensions.cs b/src/Shared/ServerInfrastructure/MemoryPoolExtensions.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/MemoryPoolExtensions.cs rename to src/Shared/ServerInfrastructure/MemoryPoolExtensions.cs diff --git a/src/Shared/ServerInfrastructure/ReadMe.md b/src/Shared/ServerInfrastructure/ReadMe.md new file mode 100644 index 0000000000..a23cac13d7 --- /dev/null +++ b/src/Shared/ServerInfrastructure/ReadMe.md @@ -0,0 +1 @@ +This folder contains shared product code that is also used with the Http2Cat test framework. diff --git a/src/Shared/ServerInfrastructure/SharedStrings.resx b/src/Shared/ServerInfrastructure/SharedStrings.resx new file mode 100644 index 0000000000..e6fe3d4ebe --- /dev/null +++ b/src/Shared/ServerInfrastructure/SharedStrings.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The client sent a {frameType} frame with length different than {expectedLength}. + + + The received frame size of {size} exceeds the limit {limit}. + + \ No newline at end of file diff --git a/src/Shared/ServerInfrastructure/SslDuplexPipe.cs b/src/Shared/ServerInfrastructure/SslDuplexPipe.cs new file mode 100644 index 0000000000..d53ced0031 --- /dev/null +++ b/src/Shared/ServerInfrastructure/SslDuplexPipe.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.IO.Pipelines; +using System.Net.Security; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; + +namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal +{ + internal class SslDuplexPipe : DuplexPipeStreamAdapter + { + public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions) + : this(transport, readerOptions, writerOptions, s => new SslStream(s)) + { + } + + public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func factory) : + base(transport, readerOptions, writerOptions, factory) + { + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/StringUtilities.cs b/src/Shared/ServerInfrastructure/StringUtilities.cs similarity index 100% rename from src/Servers/Kestrel/Core/src/Internal/Infrastructure/StringUtilities.cs rename to src/Shared/ServerInfrastructure/StringUtilities.cs