parent
40c99894e8
commit
3b7cdc166a
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
@ -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();
|
||||
|
|
@ -0,0 +1 @@
|
|||
This folder contains shared product code that is also used with the Http2Cat test framework.
|
||||
|
|
@ -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>
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue