Merge branch 'release/2.2'
This commit is contained in:
commit
d0fc981c7e
|
|
@ -134,6 +134,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Sockets.B
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Libuv.BindTests", "test\Kestrel.Transport.Libuv.BindTests\Kestrel.Transport.Libuv.BindTests.csproj", "{FB9C6B61-0A7B-4FFA-B772-A754316B262E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Libuv.BindTests", "test\Kestrel.Transport.Libuv.BindTests\Kestrel.Transport.Libuv.BindTests.csproj", "{FB9C6B61-0A7B-4FFA-B772-A754316B262E}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.H2Spec.FunctionalTests", "test\Kestrel.H2Spec.FunctionalTests\Kestrel.H2Spec.FunctionalTests.csproj", "{C4123E55-5760-4557-B89B-39E1258FD7F9}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -420,6 +422,18 @@ Global
|
||||||
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Release|x64.Build.0 = Release|Any CPU
|
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Release|x86.ActiveCfg = Release|Any CPU
|
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Release|x86.Build.0 = Release|Any CPU
|
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{C4123E55-5760-4557-B89B-39E1258FD7F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C4123E55-5760-4557-B89B-39E1258FD7F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C4123E55-5760-4557-B89B-39E1258FD7F9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{C4123E55-5760-4557-B89B-39E1258FD7F9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{C4123E55-5760-4557-B89B-39E1258FD7F9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{C4123E55-5760-4557-B89B-39E1258FD7F9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{C4123E55-5760-4557-B89B-39E1258FD7F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C4123E55-5760-4557-B89B-39E1258FD7F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C4123E55-5760-4557-B89B-39E1258FD7F9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{C4123E55-5760-4557-B89B-39E1258FD7F9}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{C4123E55-5760-4557-B89B-39E1258FD7F9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{C4123E55-5760-4557-B89B-39E1258FD7F9}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -450,6 +464,7 @@ Global
|
||||||
{B5422347-E919-431D-9EF2-C352FFE4D6C1} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
|
{B5422347-E919-431D-9EF2-C352FFE4D6C1} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
|
||||||
{9254C3EB-196B-402F-A059-34FEA6140500} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
|
{9254C3EB-196B-402F-A059-34FEA6140500} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
|
||||||
{FB9C6B61-0A7B-4FFA-B772-A754316B262E} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
|
{FB9C6B61-0A7B-4FFA-B772-A754316B262E} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
|
||||||
|
{C4123E55-5760-4557-B89B-39E1258FD7F9} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {2D10D020-6770-47CA-BB8D-2C23FE3AE071}
|
SolutionGuid = {2D10D020-6770-47CA-BB8D-2C23FE3AE071}
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ namespace SampleApp
|
||||||
var hostBuilder = new WebHostBuilder()
|
var hostBuilder = new WebHostBuilder()
|
||||||
.ConfigureLogging((_, factory) =>
|
.ConfigureLogging((_, factory) =>
|
||||||
{
|
{
|
||||||
|
factory.SetMinimumLevel(LogLevel.Debug);
|
||||||
factory.AddConsole();
|
factory.AddConsole();
|
||||||
})
|
})
|
||||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||||
|
|
|
||||||
|
|
@ -174,13 +174,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
|
||||||
builder.Append(" ");
|
builder.Append(" ");
|
||||||
}
|
}
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
|
builder.Append("{0}");
|
||||||
|
|
||||||
|
var rawDataBuilder = new StringBuilder();
|
||||||
// Write the bytes as if they were ASCII
|
// Write the bytes as if they were ASCII
|
||||||
for (int i = 0; i < buffer.Length; i++)
|
for (int i = 0; i < buffer.Length; i++)
|
||||||
{
|
{
|
||||||
builder.Append((char)buffer[i]);
|
rawDataBuilder.Append((char)buffer[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug(builder.ToString());
|
_logger.LogDebug(builder.ToString(), rawDataBuilder.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// The below APM methods call the underlying Read/WriteAsync methods which will still be logged.
|
// The below APM methods call the underlying Read/WriteAsync methods which will still be logged.
|
||||||
|
|
|
||||||
|
|
@ -104,19 +104,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task FlushAsync(IHttpOutputAborter outputAborter, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
if (_completed)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _flusher.FlushAsync(outputAborter, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Write100ContinueAsync(int streamId)
|
public Task Write100ContinueAsync(int streamId)
|
||||||
{
|
{
|
||||||
lock (_writeLock)
|
lock (_writeLock)
|
||||||
|
|
@ -171,9 +158,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
throw new InvalidOperationException(hex.Message, hex); // Report the error to the user if this was the first write.
|
throw new InvalidOperationException(hex.Message, hex); // Report the error to the user if this was the first write.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = _flusher.FlushAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task WriteResponseTrailers(int streamId, HttpResponseTrailers headers)
|
public Task WriteResponseTrailersAsync(int streamId, HttpResponseTrailers headers)
|
||||||
{
|
{
|
||||||
lock (_writeLock)
|
lock (_writeLock)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
private readonly object _dataWriterLock = new object();
|
private readonly object _dataWriterLock = new object();
|
||||||
private readonly Pipe _dataPipe;
|
private readonly Pipe _dataPipe;
|
||||||
private readonly Task _dataWriteProcessingTask;
|
private readonly Task _dataWriteProcessingTask;
|
||||||
private bool _startedWritingDataFrames;
|
|
||||||
private bool _completed;
|
private bool _completed;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
|
|
@ -74,10 +73,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Review: This is called when a CancellationToken fires mid-write. In HTTP/1.x, this aborts the entire connection.
|
// This is called when a CancellationToken fires mid-write. In HTTP/1.x, this aborts the entire connection.
|
||||||
// Should we do that here?
|
// For HTTP/2 we abort the stream.
|
||||||
void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason)
|
void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason)
|
||||||
{
|
{
|
||||||
|
_stream.ResetAndAbort(abortReason, Http2ErrorCode.INTERNAL_ERROR);
|
||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,18 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_startedWritingDataFrames)
|
return _flusher.FlushAsync(this, cancellationToken);
|
||||||
{
|
|
||||||
// If there's already been response data written to the stream, just wait for that. Any header
|
|
||||||
// should be in front of the data frames in the connection pipe. Trailers could change things.
|
|
||||||
return _flusher.FlushAsync(this, cancellationToken);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Flushing the connection pipe ensures headers already in the pipe are flushed even if no data
|
|
||||||
// frames have been written.
|
|
||||||
return _frameWriter.FlushAsync(this, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,8 +148,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
_startedWritingDataFrames = true;
|
|
||||||
|
|
||||||
_dataPipe.Writer.Write(data);
|
_dataPipe.Writer.Write(data);
|
||||||
return _flusher.FlushAsync(this, cancellationToken);
|
return _flusher.FlushAsync(this, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
@ -211,7 +198,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
await _frameWriter.WriteDataAsync(_streamId, _flowControl, _stream.MinResponseDataRate, readResult.Buffer, endStream: false);
|
await _frameWriter.WriteDataAsync(_streamId, _flowControl, _stream.MinResponseDataRate, readResult.Buffer, endStream: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _frameWriter.WriteResponseTrailers(_streamId, _stream.Trailers);
|
await _frameWriter.WriteResponseTrailersAsync(_streamId, _stream.Trailers);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -427,7 +427,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
ResetAndAbort(abortReason, Http2ErrorCode.INTERNAL_ERROR);
|
ResetAndAbort(abortReason, Http2ErrorCode.INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetAndAbort(ConnectionAbortedException abortReason, Http2ErrorCode error)
|
internal void ResetAndAbort(ConnectionAbortedException abortReason, Http2ErrorCode error)
|
||||||
{
|
{
|
||||||
// Future incoming frames will drain for a default grace period to avoid destabilizing the connection.
|
// Future incoming frames will drain for a default grace period to avoid destabilizing the connection.
|
||||||
var states = ApplyCompletionFlag(StreamCompletionFlags.Aborted);
|
var states = ApplyCompletionFlag(StreamCompletionFlags.Aborted);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// 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.Net.Sockets;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
||||||
|
{
|
||||||
|
internal static class NativeMethods
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
private static extern bool SetHandleInformation(IntPtr hObject, HANDLE_FLAGS dwMask, HANDLE_FLAGS dwFlags);
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
private enum HANDLE_FLAGS : uint
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
INHERIT = 1,
|
||||||
|
PROTECT_FROM_CLOSE = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void DisableHandleInheritance(Socket socket)
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
SetHandleInformation(socket.Handle, HANDLE_FLAGS.INHERIT, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -80,6 +80,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
|
||||||
IPEndPoint endPoint = _endPointInformation.IPEndPoint;
|
IPEndPoint endPoint = _endPointInformation.IPEndPoint;
|
||||||
|
|
||||||
var listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
var listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||||
|
NativeMethods.DisableHandleInheritance(listenSocket);
|
||||||
|
|
||||||
// Kestrel expects IPv6Any to bind to both IPv6 and IPv4
|
// Kestrel expects IPv6Any to bind to both IPv6 and IPv4
|
||||||
if (endPoint.Address == IPAddress.IPv6Any)
|
if (endPoint.Address == IPAddress.IPv6Any)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
namespace H2Spec.FunctionalTests
|
||||||
{
|
{
|
||||||
public static class H2SpecCommands
|
public static class H2SpecCommands
|
||||||
{
|
{
|
||||||
|
|
@ -1,33 +1,35 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
#if NETCOREAPP2_2
|
using System;
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
using Microsoft.AspNetCore.Testing;
|
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
|
namespace H2Spec.FunctionalTests
|
||||||
{
|
{
|
||||||
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")]
|
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")]
|
||||||
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81,
|
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81,
|
||||||
SkipReason = "Missing Windows ALPN support: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation#Support")]
|
SkipReason = "Missing Windows ALPN support: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation#Support")]
|
||||||
public class H2SpecTests : TestApplicationErrorLoggerLoggedTest
|
public class H2SpecTests : LoggedTest
|
||||||
{
|
{
|
||||||
|
private static readonly string _testCertPath = Path.Combine(Directory.GetCurrentDirectory(), "shared", "TestCertificates", "testCert.pfx");
|
||||||
|
|
||||||
[ConditionalTheory]
|
[ConditionalTheory]
|
||||||
[MemberData(nameof(H2SpecTestCases))]
|
[MemberData(nameof(H2SpecTestCases))]
|
||||||
public async Task RunIndividualTestCase(H2SpecTestCase testCase)
|
public async Task RunIndividualTestCase(H2SpecTestCase testCase)
|
||||||
{
|
{
|
||||||
var hostBuilder = TransportSelector.GetWebHostBuilder()
|
var hostBuilder = new WebHostBuilder()
|
||||||
.UseKestrel(options =>
|
.UseKestrel(options =>
|
||||||
{
|
{
|
||||||
options.Listen(IPAddress.Loopback, 0, listenOptions =>
|
options.Listen(IPAddress.Loopback, 0, listenOptions =>
|
||||||
|
|
@ -35,7 +37,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
|
||||||
listenOptions.Protocols = HttpProtocols.Http2;
|
listenOptions.Protocols = HttpProtocols.Http2;
|
||||||
if (testCase.Https)
|
if (testCase.Https)
|
||||||
{
|
{
|
||||||
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
|
listenOptions.UseHttps(_testCertPath, "testPassword");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
@ -46,7 +48,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
|
||||||
{
|
{
|
||||||
await host.StartAsync();
|
await host.StartAsync();
|
||||||
|
|
||||||
H2SpecCommands.RunTest(testCase.Id, host.GetPort(), testCase.Https, Logger);
|
H2SpecCommands.RunTest(testCase.Id, GetPort(host), testCase.Https, Logger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,9 +131,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
|
||||||
await context.Response.WriteAsync("Hello World");
|
await context.Response.WriteAsync("Hello World");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int GetPort(IWebHost host)
|
||||||
|
{
|
||||||
|
return host.ServerFeatures.Get<IServerAddressesFeature>().Addresses
|
||||||
|
.Select(a => new Uri(a))
|
||||||
|
.First()
|
||||||
|
.Port;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#elif NET461 // HTTP/2 is not supported
|
|
||||||
#else
|
|
||||||
#error TFMs need updating
|
|
||||||
#endif
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<AssemblyName>H2Spec.FunctionalTests</AssemblyName>
|
||||||
|
<RootNamespace>H2Spec.FunctionalTests</RootNamespace>
|
||||||
|
<!-- h2spec doesn't run on the desktop framework where ALPN isn't available. -->
|
||||||
|
<!-- DeveloperBuildTestTfms is StandardTestTfms minus desktop. -->
|
||||||
|
<TargetFrameworks>$(DeveloperBuildTestTfms)</TargetFrameworks>
|
||||||
|
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||||
|
<TestGroupName>H2Spec.FunctionalTests</TestGroupName>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="..\shared\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Kestrel\Kestrel.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\Kestrel.Https\Kestrel.Https.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Internal.AspNetCore.H2Spec.All" Version="$(MicrosoftInternalAspNetCoreH2SpecAllPackageVersion)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -1998,5 +1998,139 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await WaitForConnectionErrorAsync<HPackEncodingException>(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.INTERNAL_ERROR,
|
await WaitForConnectionErrorAsync<HPackEncodingException>(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.INTERNAL_ERROR,
|
||||||
CoreStrings.HPackErrorNotEnoughBuffer);
|
CoreStrings.HPackErrorNotEnoughBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WriteAsync_PreCancelledCancellationToken_DoesNotAbort()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
};
|
||||||
|
await InitializeConnectionAsync(async context =>
|
||||||
|
{
|
||||||
|
// The cancellation is checked at the start of WriteAsync and no application state is changed.
|
||||||
|
await Assert.ThrowsAsync<OperationCanceledException>(() => context.Response.WriteAsync("hello,", new CancellationToken(true)));
|
||||||
|
Assert.False(context.Response.HasStarted);
|
||||||
|
});
|
||||||
|
|
||||||
|
await StartStreamAsync(1, headers, endStream: true);
|
||||||
|
|
||||||
|
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 55,
|
||||||
|
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||||
|
withStreamId: 1);
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||||
|
withStreamId: 1);
|
||||||
|
|
||||||
|
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||||
|
|
||||||
|
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||||
|
|
||||||
|
Assert.Equal(3, _decodedHeaders.Count);
|
||||||
|
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WriteAsync_CancellationTokenTriggeredDueToFlowControl_SendRST()
|
||||||
|
{
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
var writeStarted = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
};
|
||||||
|
await InitializeConnectionAsync(async context =>
|
||||||
|
{
|
||||||
|
await context.Response.Body.FlushAsync(); // https://github.com/aspnet/KestrelHttpServer/issues/3031
|
||||||
|
var writeTask = context.Response.WriteAsync("hello,", cts.Token);
|
||||||
|
writeStarted.SetResult(0);
|
||||||
|
await Assert.ThrowsAsync<OperationCanceledException>(() => writeTask);
|
||||||
|
});
|
||||||
|
|
||||||
|
_clientSettings.InitialWindowSize = 0;
|
||||||
|
await SendSettingsAsync();
|
||||||
|
await ExpectAsync(Http2FrameType.SETTINGS,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2SettingsFrameFlags.ACK,
|
||||||
|
withStreamId: 0);
|
||||||
|
|
||||||
|
await StartStreamAsync(1, headers, endStream: true);
|
||||||
|
|
||||||
|
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 37,
|
||||||
|
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||||
|
withStreamId: 1);
|
||||||
|
|
||||||
|
await writeStarted.Task;
|
||||||
|
|
||||||
|
cts.Cancel();
|
||||||
|
|
||||||
|
await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, null);
|
||||||
|
|
||||||
|
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||||
|
|
||||||
|
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||||
|
|
||||||
|
Assert.Equal(2, _decodedHeaders.Count);
|
||||||
|
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResponseHeaders_NotBlockedByFlowControl()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
};
|
||||||
|
await InitializeConnectionAsync(context =>
|
||||||
|
{
|
||||||
|
return context.Response.WriteAsync("hello world");
|
||||||
|
});
|
||||||
|
|
||||||
|
_clientSettings.InitialWindowSize = 0;
|
||||||
|
await SendSettingsAsync();
|
||||||
|
await ExpectAsync(Http2FrameType.SETTINGS,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2SettingsFrameFlags.ACK,
|
||||||
|
withStreamId: 0);
|
||||||
|
|
||||||
|
await StartStreamAsync(1, headers, endStream: true);
|
||||||
|
|
||||||
|
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 37,
|
||||||
|
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||||
|
withStreamId: 1);
|
||||||
|
|
||||||
|
await SendWindowUpdateAsync(1, 11);
|
||||||
|
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 11,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.NONE,
|
||||||
|
withStreamId: 1);
|
||||||
|
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||||
|
withStreamId: 1);
|
||||||
|
|
||||||
|
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||||
|
|
||||||
|
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||||
|
|
||||||
|
Assert.Equal(2, _decodedHeaders.Count);
|
||||||
|
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
|
{
|
||||||
|
public class HandleInheritanceTests : TestApplicationErrorLoggerLoggedTest
|
||||||
|
{
|
||||||
|
[ConditionalFact]
|
||||||
|
[OSSkipCondition(OperatingSystems.Linux, SkipReason = "Fixed in 3.0 https://github.com/aspnet/KestrelHttpServer/issues/3040")]
|
||||||
|
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Fixed in 3.0 https://github.com/aspnet/KestrelHttpServer/issues/3040")]
|
||||||
|
public async Task SpawnChildProcess_DoesNotInheritListenHandle()
|
||||||
|
{
|
||||||
|
var hostBuilder = TransportSelector.GetWebHostBuilder()
|
||||||
|
.UseKestrel()
|
||||||
|
.ConfigureServices(AddTestLogging)
|
||||||
|
.UseUrls("http://127.0.0.1:0")
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
return context.Response.WriteAsync("Hello World");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var host = hostBuilder.Build())
|
||||||
|
{
|
||||||
|
await host.StartAsync();
|
||||||
|
|
||||||
|
var processInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "cmd.exe",
|
||||||
|
CreateNoWindow = true,
|
||||||
|
};
|
||||||
|
using (var process = Process.Start(processInfo))
|
||||||
|
{
|
||||||
|
var port = host.GetPort();
|
||||||
|
await host.StopAsync();
|
||||||
|
|
||||||
|
// We should not be able to connect if the handle was correctly closed and not inherited by the child process.
|
||||||
|
using (var client = new TcpClient())
|
||||||
|
{
|
||||||
|
await Assert.ThrowsAnyAsync<SocketException>(() => client.ConnectAsync("127.0.0.1", port));
|
||||||
|
}
|
||||||
|
|
||||||
|
process.Kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -51,7 +51,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
|
|
||||||
private static readonly string[] _requestLines = new[]
|
private static readonly string[] _requestLines = new[]
|
||||||
{
|
{
|
||||||
"POST / HTTP/1.0\r\n",
|
"POST / HTTP/1.1\r\n",
|
||||||
|
"Host: \r\n",
|
||||||
$"Content-Length: {_dataLength}\r\n",
|
$"Content-Length: {_dataLength}\r\n",
|
||||||
"\r\n"
|
"\r\n"
|
||||||
};
|
};
|
||||||
|
|
@ -60,11 +61,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
var totalHeaderSize = 0;
|
||||||
|
|
||||||
|
for (var i = 1; i < _requestLines.Length - 1; i++)
|
||||||
|
{
|
||||||
|
totalHeaderSize += _requestLines[i].Length;
|
||||||
|
}
|
||||||
|
|
||||||
var maxRequestBufferSizeValues = new Tuple<long?, bool>[] {
|
var maxRequestBufferSizeValues = new Tuple<long?, bool>[] {
|
||||||
// Smallest buffer that can hold a test request line without causing
|
// Smallest buffer that can hold the test request headers without causing
|
||||||
// the server to hang waiting for the end of the request line or
|
// the server to hang waiting for the end of the request line or
|
||||||
// a header line.
|
// a header line.
|
||||||
Tuple.Create((long?)(_requestLines.Max(line => line.Length)), true),
|
Tuple.Create((long?)totalHeaderSize, true),
|
||||||
|
|
||||||
// Small buffer, but large enough to hold all request headers.
|
// Small buffer, but large enough to hold all request headers.
|
||||||
Tuple.Create((long?)16 * 1024, true),
|
Tuple.Create((long?)16 * 1024, true),
|
||||||
|
|
@ -194,11 +202,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
startReadingRequestBody.TrySetResult(null);
|
startReadingRequestBody.TrySetResult(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var reader = new StreamReader(stream, Encoding.ASCII))
|
await AssertStreamContains(stream, $"bytesRead: {data.Length}");
|
||||||
{
|
|
||||||
var response = reader.ReadToEnd();
|
|
||||||
Assert.Contains($"bytesRead: {data.Length}", response);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -374,5 +378,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// THIS IS NOT GENERAL PURPOSE. If the initial characters could repeat, this is broken. However, since we're
|
||||||
|
// looking for /bytesWritten: \d+/ and the initial "b" cannot occur elsewhere in the pattern, this works.
|
||||||
|
private static async Task AssertStreamContains(Stream stream, string expectedSubstring)
|
||||||
|
{
|
||||||
|
var expectedBytes = Encoding.ASCII.GetBytes(expectedSubstring);
|
||||||
|
var exptectedLength = expectedBytes.Length;
|
||||||
|
var responseBuffer = new byte[exptectedLength];
|
||||||
|
|
||||||
|
var matchedChars = 0;
|
||||||
|
|
||||||
|
while (matchedChars < exptectedLength)
|
||||||
|
{
|
||||||
|
var count = await stream.ReadAsync(responseBuffer, 0, exptectedLength - matchedChars).DefaultTimeout();
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
Assert.True(false, "Stream completed without expected substring.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < count && matchedChars < exptectedLength; i++)
|
||||||
|
{
|
||||||
|
if (responseBuffer[i] == expectedBytes[matchedChars])
|
||||||
|
{
|
||||||
|
matchedChars++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
matchedChars = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
// https://github.com/aspnet/KestrelHttpServer/issues/520#issuecomment-188591242
|
// https://github.com/aspnet/KestrelHttpServer/issues/520#issuecomment-188591242
|
||||||
// will be lost.
|
// will be lost.
|
||||||
[InlineData((long)int.MaxValue + 1, false)]
|
[InlineData((long)int.MaxValue + 1, false)]
|
||||||
public void LargeUpload(long contentLength, bool checkBytes)
|
public async Task LargeUpload(long contentLength, bool checkBytes)
|
||||||
{
|
{
|
||||||
const int bufferLength = 1024 * 1024;
|
const int bufferLength = 1024 * 1024;
|
||||||
Assert.True(contentLength % bufferLength == 0, $"{nameof(contentLength)} sent must be evenly divisible by {bufferLength}.");
|
Assert.True(contentLength % bufferLength == 0, $"{nameof(contentLength)} sent must be evenly divisible by {bufferLength}.");
|
||||||
|
|
@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
total += received;
|
total += received;
|
||||||
}
|
}
|
||||||
|
|
||||||
await context.Response.WriteAsync(total.ToString(CultureInfo.InvariantCulture));
|
await context.Response.WriteAsync($"bytesRead: {total.ToString()}");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -100,8 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
|
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
|
||||||
{
|
{
|
||||||
socket.Connect(new IPEndPoint(IPAddress.Loopback, host.GetPort()));
|
socket.Connect(new IPEndPoint(IPAddress.Loopback, host.GetPort()));
|
||||||
socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.0\r\n"));
|
socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.1\r\nHost: \r\n"));
|
||||||
Thread.Sleep(5000);
|
|
||||||
socket.Send(Encoding.ASCII.GetBytes($"Content-Length: {contentLength}\r\n\r\n"));
|
socket.Send(Encoding.ASCII.GetBytes($"Content-Length: {contentLength}\r\n\r\n"));
|
||||||
|
|
||||||
var contentBytes = new byte[bufferLength];
|
var contentBytes = new byte[bufferLength];
|
||||||
|
|
@ -119,15 +118,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
socket.Send(contentBytes);
|
socket.Send(contentBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = new StringBuilder();
|
using (var stream = new NetworkStream(socket))
|
||||||
var responseBytes = new byte[4096];
|
|
||||||
var received = 0;
|
|
||||||
while ((received = socket.Receive(responseBytes)) > 0)
|
|
||||||
{
|
{
|
||||||
response.Append(Encoding.ASCII.GetString(responseBytes, 0, received));
|
await AssertStreamContains(stream, $"bytesRead: {contentLength}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Contains(contentLength.ToString(CultureInfo.InvariantCulture), response.ToString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -906,5 +900,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
Assert.NotEmpty(facts["RemotePort"].Value<string>());
|
Assert.NotEmpty(facts["RemotePort"].Value<string>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// THIS IS NOT GENERAL PURPOSE. If the initial characters could repeat, this is broken. However, since we're
|
||||||
|
// looking for /bytesWritten: \d+/ and the initial "b" cannot occur elsewhere in the pattern, this works.
|
||||||
|
private static async Task AssertStreamContains(Stream stream, string expectedSubstring)
|
||||||
|
{
|
||||||
|
var expectedBytes = Encoding.ASCII.GetBytes(expectedSubstring);
|
||||||
|
var exptectedLength = expectedBytes.Length;
|
||||||
|
var responseBuffer = new byte[exptectedLength];
|
||||||
|
|
||||||
|
var matchedChars = 0;
|
||||||
|
|
||||||
|
while (matchedChars < exptectedLength)
|
||||||
|
{
|
||||||
|
var count = await stream.ReadAsync(responseBuffer, 0, exptectedLength - matchedChars).DefaultTimeout();
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
Assert.True(false, "Stream completed without expected substring.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < count && matchedChars < exptectedLength; i++)
|
||||||
|
{
|
||||||
|
if (responseBuffer[i] == expectedBytes[matchedChars])
|
||||||
|
{
|
||||||
|
matchedChars++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
matchedChars = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\Kestrel.Transport.FunctionalTests\**\*.cs" />
|
|
||||||
<Compile Include="..\shared\*.cs" LinkBase="shared" />
|
<Compile Include="..\shared\*.cs" LinkBase="shared" />
|
||||||
<Compile Include="..\shared\TransportTestHelpers\*.cs" LinkBase="shared\TransportTestHelpers" />
|
<Compile Include="..\shared\TransportTestHelpers\*.cs" LinkBase="shared\TransportTestHelpers" />
|
||||||
<Content Include="..\shared\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
|
<Content Include="..\shared\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue