Generalize Http2Cat #14894 (#17438)

This commit is contained in:
Chris Ross 2019-12-03 16:16:52 -08:00 committed by GitHub
parent 40c99894e8
commit 3b7cdc166a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 902 additions and 233 deletions

View File

@ -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}

View File

@ -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();
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@ -6,8 +6,17 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(SharedSourceRoot)Http2\**\*.cs" LinkBase="Shared\Http2\" />
<Compile Include="$(SharedSourceRoot)Http2cat\**\*.cs" LinkBase="Shared\Http2cat" />
<Compile Include="$(SharedSourceRoot)ServerInfrastructure\**\*.cs" LinkBase="Shared\" />
<Compile Include="$(SharedSourceRoot)TaskToApm.cs" Link="Shared\TaskToApm.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Server.HttpSys" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" />
<Reference Include="Microsoft.Extensions.Hosting" />
<Reference Include="System.Net.Http.WinHttpHandler" />
</ItemGroup>
@ -22,4 +31,15 @@
<HelixPreCommand Include="call RunPowershell.cmd UpdateIISExpressCertificate.ps1 || exit /b 1" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(SharedSourceRoot)ServerInfrastructure\SharedStrings.resx" Link="Shared\SharedStrings.resx">
<ManifestResourceName>Microsoft.AspNetCore.Server.SharedStrings</ManifestResourceName>
<Generator></Generator>
</EmbeddedResource>
<EmbeddedResource Include="$(SharedSourceRoot)Http2\SR.resx" Link="Shared\Http2\SR.resx">
<ManifestResourceName>System.Net.Http.SR</ManifestResourceName>
<Generator></Generator>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@ -15,7 +15,8 @@
<Compile Include="$(SharedSourceRoot)CertificateGeneration\**\*.cs" />
<Compile Include="$(SharedSourceRoot)ValueTaskExtensions\**\*.cs" />
<Compile Include="$(SharedSourceRoot)UrlDecoder\**\*.cs" />
<Compile Include="$(SharedSourceRoot)Http2\**\*.cs" Link="Shared\Http2\%(Filename)%(Extension)" />
<Compile Include="$(SharedSourceRoot)Http2\**\*.cs" LinkBase="Shared\Http2\" />
<Compile Include="$(SharedSourceRoot)ServerInfrastructure\**\*.cs" LinkBase="Shared\" />
<Compile Include="$(RepoRoot)src\Shared\TaskToApm.cs" Link="Internal\TaskToApm.cs" />
</ItemGroup>
@ -35,6 +36,10 @@
<EmbeddedResource Update="CoreStrings.resx">
<Generator></Generator>
</EmbeddedResource>
<EmbeddedResource Include="$(SharedSourceRoot)ServerInfrastructure\SharedStrings.resx" Link="Shared\SharedStrings.resx">
<ManifestResourceName>Microsoft.AspNetCore.Server.SharedStrings</ManifestResourceName>
<Generator></Generator>
</EmbeddedResource>
<EmbeddedResource Include="$(SharedSourceRoot)Http2\SR.resx" Link="Shared\Http2\SR.resx">
<ManifestResourceName>System.Net.Http.SR</ManifestResourceName>
<Generator></Generator>

View File

@ -291,19 +291,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
return new X509Certificate2(certificate);
}
private class SslDuplexPipe : DuplexPipeStreamAdapter<SslStream>
{
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<Stream, SslStream> factory) :
base(transport, readerOptions, writerOptions, factory)
{
}
}
}
}

View File

@ -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")]

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@ -13,7 +13,6 @@
<Compile Include="$(KestrelSharedSourceRoot)KnownHeaders.cs" LinkBase="shared" />
<Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
<Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
<Compile Include="$(KestrelSharedSourceRoot)\DuplexPipe.cs" Link="Internal\DuplexPipe.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\CorrelationIdGenerator.cs" Link="Internal\CorrelationIdGenerator.cs" />
<Compile Include="$(SharedSourceRoot)test\Shared.Tests\Http2\**\*.cs" Link="Shared\Http2\%(Filename)%(Extension)" />
</ItemGroup>

View File

@ -13,7 +13,7 @@
<ItemGroup>
<Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
<Compile Include="$(KestrelSharedSourceRoot)\CorrelationIdGenerator.cs" Link="Internal\CorrelationIdGenerator.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\DuplexPipe.cs" Link="Internal\DuplexPipe.cs" />
<Compile Include="$(SharedSourceRoot)ServerInfrastructure\DuplexPipe.cs" Link="Internal\DuplexPipe.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.cs" Link="Internal\TransportConnection.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" Link="Internal\TransportConnection.Generated.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.FeatureCollection.cs" Link="Internal\TransportConnection.FeatureCollection.cs" />

View File

@ -13,7 +13,7 @@
<ItemGroup>
<Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
<Compile Include="$(KestrelSharedSourceRoot)\CorrelationIdGenerator.cs" Link="Internal\CorrelationIdGenerator.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\DuplexPipe.cs" Link="Internal\DuplexPipe.cs" />
<Compile Include="$(SharedSourceRoot)ServerInfrastructure\DuplexPipe.cs" Link="Internal\DuplexPipe.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.cs" Link="Internal\TransportConnection.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" Link="Internal\TransportConnection.Generated.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.FeatureCollection.cs" Link="Internal\TransportConnection.FeatureCollection.cs" />

View File

@ -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<Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions> options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
[System.Diagnostics.DebuggerStepThroughAttribute]
public System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> ConnectAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
}
}

View File

@ -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<byte> _memoryPool;

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Managed socket transport for the ASP.NET Core Kestrel cross-platform web server.</Description>
@ -14,7 +14,7 @@
<ItemGroup>
<Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
<Compile Include="$(KestrelSharedSourceRoot)\CorrelationIdGenerator.cs" Link="Internal\CorrelationIdGenerator.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\DuplexPipe.cs" Link="Internal\DuplexPipe.cs" />
<Compile Include="$(SharedSourceRoot)ServerInfrastructure\DuplexPipe.cs" Link="Internal\DuplexPipe.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.cs" Link="Internal\TransportConnection.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" Link="Internal\TransportConnection.Generated.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.FeatureCollection.cs" Link="Internal\TransportConnection.FeatureCollection.cs" />
@ -26,10 +26,6 @@
<Reference Include="Microsoft.Extensions.Options" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="http2cat" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="SocketsStrings.resx">
<Generator></Generator>

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@ -16,7 +16,6 @@
<Compile Include="$(KestrelSharedSourceRoot)test\TestKestrelTrace.cs" />
<Compile Include="..\..\Transport.Sockets\src\Internal\IOQueue.cs" Link="Internal\IOQueue.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\CorrelationIdGenerator.cs" Link="Internal\CorrelationIdGenerator.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\DuplexPipe.cs" Link="Internal\DuplexPipe.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.cs" Link="Internal\TransportConnection.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" Link="Internal\TransportConnection.Generated.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.FeatureCollection.cs" Link="Internal\TransportConnection.FeatureCollection.cs" />

View File

@ -28,13 +28,13 @@ namespace Http2SampleApp
var basePort = context.Configuration.GetValue<int?>("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;
});

View File

@ -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<IConnectionFactory, SocketConnectionFactory>();
services.AddSingleton<Http2CatHostedService>();
})
.UseHttp2Cat("https://localhost:5001", RunTestCase)
.Build();
await host.Services.GetService<Http2CatHostedService>().RunAsync();
await host.RunAsync();
}
private class Http2CatHostedService
internal static async Task RunTestCase(Http2Utilities h2Connection)
{
private readonly IConnectionFactory _connectionFactory;
private readonly ILogger<Http2CatHostedService> _logger;
await h2Connection.InitializeConnectionAsync();
public Http2CatHostedService(IConnectionFactory connectionFactory, ILogger<Http2CatHostedService> 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<IMemoryPoolFeature>()?.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> { 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<SslStream>
{
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<Stream, SslStream> factory) :
base(transport, readerOptions, writerOptions, factory)
{
}
await h2Connection.StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
h2Connection.Logger.LogInformation("Connection stopped.");
}
}
}

View File

@ -1,15 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp5.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(SharedSourceRoot)Http2\**\*.cs" LinkBase="Shared\Http2\" />
<Compile Include="$(SharedSourceRoot)Http2cat\**\*.cs" LinkBase="Shared\Http2cat" />
<Compile Include="$(SharedSourceRoot)ServerInfrastructure\**\*.cs" LinkBase="Shared\" />
<Compile Include="$(SharedSourceRoot)TaskToApm.cs" Link="Shared\TaskToApm.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Core" />
<Reference Include="Microsoft.Extensions.Hosting" />
<Reference Include="Microsoft.Extensions.Logging.Console" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(SharedSourceRoot)ServerInfrastructure\SharedStrings.resx" Link="Shared\SharedStrings.resx">
<ManifestResourceName>Microsoft.AspNetCore.Server.SharedStrings</ManifestResourceName>
<Generator></Generator>
</EmbeddedResource>
<EmbeddedResource Include="$(SharedSourceRoot)Http2\SR.resx" Link="Shared\Http2\SR.resx">
<ManifestResourceName>System.Net.Http.SR</ManifestResourceName>
<Generator></Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="TaskTimeoutExtensions.cs" />
</ItemGroup>
</Project>

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@ -14,7 +14,6 @@
<Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
<Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
<Compile Include="$(KestrelSharedSourceRoot)\CorrelationIdGenerator.cs" Link="Internal\CorrelationIdGenerator.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\DuplexPipe.cs" Link="Internal\DuplexPipe.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.cs" Link="Internal\TransportConnection.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" Link="Internal\TransportConnection.Generated.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.FeatureCollection.cs" Link="Internal\TransportConnection.FeatureCollection.cs" />

View File

@ -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<Http2CatHostedService> _logger;
private readonly CancellationTokenSource _stopTokenSource = new CancellationTokenSource();
private Task _backgroundTask;
public Http2CatHostedService(IConnectionFactory connectionFactory, ILogger<Http2CatHostedService> logger,
IOptions<Http2CatOptions> 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<IMemoryPoolFeature>()?.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> { 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();
}
}
}
}

View File

@ -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<Http2Utilities, Task> scenario)
{
hostBuilder.ConfigureServices(services =>
{
services.UseHttp2Cat(options =>
{
options.Url = address;
options.Scenaro = scenario;
});
});
return hostBuilder;
}
}
}

View File

@ -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<Http2CatOptions> configureOptions)
{
services.AddSingleton<IConnectionFactory, SocketConnectionFactory>();
services.AddHostedService<Http2CatHostedService>();
services.Configure(configureOptions);
return services;
}
}
}

View File

@ -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<Http2Utilities, Task> Scenaro { get; set; }
}
}

View File

@ -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.

View File

@ -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<byte> 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<KeyValuePair<string, string>> _browserRequestHeaders = new[]
public static readonly IEnumerable<KeyValuePair<string, string>> BrowserRequestHeaders = new[]
{
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
new KeyValuePair<string, string>(HeaderNames.Scheme, "https"),
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:443"),
new KeyValuePair<string, string>("user-agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"),
new KeyValuePair<string, string>("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
new KeyValuePair<string, string>("accept-language", "en-US,en;q=0.5"),
new KeyValuePair<string, string>("accept-encoding", "gzip, deflate, br"),
new KeyValuePair<string, string>("upgrade-insecure-requests", "1"),
};
public static readonly IEnumerable<KeyValuePair<string, string>> BrowserRequestHeadersHttp = new[]
{
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
new KeyValuePair<string, string>("user-agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"),
new KeyValuePair<string, string>("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
@ -40,7 +55,7 @@ namespace http2cat
new KeyValuePair<string, string>("upgrade-insecure-requests", "1"),
};
public static readonly IEnumerable<KeyValuePair<string, string>> _postRequestHeaders = new[]
public static readonly IEnumerable<KeyValuePair<string, string>> PostRequestHeaders = new[]
{
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
@ -48,7 +63,7 @@ namespace http2cat
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
};
public static readonly IEnumerable<KeyValuePair<string, string>> _expectContinueRequestHeaders = new[]
public static readonly IEnumerable<KeyValuePair<string, string>> ExpectContinueRequestHeaders = new[]
{
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
@ -57,37 +72,37 @@ namespace http2cat
new KeyValuePair<string, string>("expect", "100-continue"),
};
public static readonly IEnumerable<KeyValuePair<string, string>> _requestTrailers = new[]
public static readonly IEnumerable<KeyValuePair<string, string>> RequestTrailers = new[]
{
new KeyValuePair<string, string>("trailer-one", "1"),
new KeyValuePair<string, string>("trailer-two", "2"),
};
public static readonly IEnumerable<KeyValuePair<string, string>> _oneContinuationRequestHeaders = new[]
public static readonly IEnumerable<KeyValuePair<string, string>> OneContinuationRequestHeaders = new[]
{
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
new KeyValuePair<string, string>(HeaderNames.Scheme, "https"),
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
new KeyValuePair<string, string>("a", _4kHeaderValue),
new KeyValuePair<string, string>("b", _4kHeaderValue),
new KeyValuePair<string, string>("c", _4kHeaderValue),
new KeyValuePair<string, string>("d", _4kHeaderValue)
new KeyValuePair<string, string>("a", FourKHeaderValue),
new KeyValuePair<string, string>("b", FourKHeaderValue),
new KeyValuePair<string, string>("c", FourKHeaderValue),
new KeyValuePair<string, string>("d", FourKHeaderValue)
};
public static readonly IEnumerable<KeyValuePair<string, string>> _twoContinuationsRequestHeaders = new[]
public static readonly IEnumerable<KeyValuePair<string, string>> TwoContinuationsRequestHeaders = new[]
{
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
new KeyValuePair<string, string>(HeaderNames.Scheme, "https"),
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
new KeyValuePair<string, string>("a", _4kHeaderValue),
new KeyValuePair<string, string>("b", _4kHeaderValue),
new KeyValuePair<string, string>("c", _4kHeaderValue),
new KeyValuePair<string, string>("d", _4kHeaderValue),
new KeyValuePair<string, string>("e", _4kHeaderValue),
new KeyValuePair<string, string>("f", _4kHeaderValue),
new KeyValuePair<string, string>("g", _4kHeaderValue),
new KeyValuePair<string, string>("a", FourKHeaderValue),
new KeyValuePair<string, string>("b", FourKHeaderValue),
new KeyValuePair<string, string>("c", FourKHeaderValue),
new KeyValuePair<string, string>("d", FourKHeaderValue),
new KeyValuePair<string, string>("e", FourKHeaderValue),
new KeyValuePair<string, string>("f", FourKHeaderValue),
new KeyValuePair<string, string>("g", FourKHeaderValue),
};
public static IEnumerable<KeyValuePair<string, string>> 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<byte> name, ReadOnlySpan<byte> value)
{
_decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetAsciiOrUTF8StringNonNullCharacters();
_decodedHeaders[GetAsciiStringNonNullCharacters(name)] = GetAsciiOrUTF8StringNonNullCharacters(value);
}
public unsafe string GetAsciiStringNonNullCharacters(ReadOnlySpan<byte> 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<byte> 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<Http2PeerSetting> settings, Span<byte> 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);
}

View File

@ -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<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout,
private static async Task<T> TimeoutAfter<T>(this Task<T> 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)
{

View File

@ -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();

View File

@ -0,0 +1 @@
This folder contains shared product code that is also used with the Http2Cat test framework.

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Http2ErrorUnexpectedFrameLength" xml:space="preserve">
<value>The client sent a {frameType} frame with length different than {expectedLength}.</value>
</data>
<data name="Http2ErrorFrameOverLimit" xml:space="preserve">
<value>The received frame size of {size} exceeds the limit {limit}.</value>
</data>
</root>

View File

@ -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<SslStream>
{
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<Stream, SslStream> factory) :
base(transport, readerOptions, writerOptions, factory)
{
}
}
}