Add in-memory functional test project (#2780)

- Run AddressRegistrationTests independently to avoid port conflicts
- Only run tests that verify transport behavior for each transport
This commit is contained in:
Stephen Halter 2018-08-07 15:34:11 -07:00 committed by GitHub
parent b494e50353
commit 0e99235d59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 4849 additions and 4231 deletions

View File

@ -128,6 +128,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlaintextApp", "samples\Pla
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformBenchmarks", "benchmarkapps\PlatformBenchmarks\PlatformBenchmarks.csproj", "{7C24EAB8-57A9-4613-A8A6-4C21BB7D260D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.InMemory.FunctionalTests", "test\Kestrel.InMemory.FunctionalTests\Kestrel.InMemory.FunctionalTests.csproj", "{B5422347-E919-431D-9EF2-C352FFE4D6C1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Sockets.BindTests", "test\Kestrel.Transport.Sockets.BindTests\Kestrel.Transport.Sockets.BindTests.csproj", "{9254C3EB-196B-402F-A059-34FEA6140500}"
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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -378,6 +384,42 @@ Global
{7C24EAB8-57A9-4613-A8A6-4C21BB7D260D}.Release|x64.Build.0 = Release|Any CPU
{7C24EAB8-57A9-4613-A8A6-4C21BB7D260D}.Release|x86.ActiveCfg = Release|Any CPU
{7C24EAB8-57A9-4613-A8A6-4C21BB7D260D}.Release|x86.Build.0 = Release|Any CPU
{B5422347-E919-431D-9EF2-C352FFE4D6C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5422347-E919-431D-9EF2-C352FFE4D6C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5422347-E919-431D-9EF2-C352FFE4D6C1}.Debug|x64.ActiveCfg = Debug|Any CPU
{B5422347-E919-431D-9EF2-C352FFE4D6C1}.Debug|x64.Build.0 = Debug|Any CPU
{B5422347-E919-431D-9EF2-C352FFE4D6C1}.Debug|x86.ActiveCfg = Debug|Any CPU
{B5422347-E919-431D-9EF2-C352FFE4D6C1}.Debug|x86.Build.0 = Debug|Any CPU
{B5422347-E919-431D-9EF2-C352FFE4D6C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5422347-E919-431D-9EF2-C352FFE4D6C1}.Release|Any CPU.Build.0 = Release|Any CPU
{B5422347-E919-431D-9EF2-C352FFE4D6C1}.Release|x64.ActiveCfg = Release|Any CPU
{B5422347-E919-431D-9EF2-C352FFE4D6C1}.Release|x64.Build.0 = Release|Any CPU
{B5422347-E919-431D-9EF2-C352FFE4D6C1}.Release|x86.ActiveCfg = Release|Any CPU
{B5422347-E919-431D-9EF2-C352FFE4D6C1}.Release|x86.Build.0 = Release|Any CPU
{9254C3EB-196B-402F-A059-34FEA6140500}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9254C3EB-196B-402F-A059-34FEA6140500}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9254C3EB-196B-402F-A059-34FEA6140500}.Debug|x64.ActiveCfg = Debug|Any CPU
{9254C3EB-196B-402F-A059-34FEA6140500}.Debug|x64.Build.0 = Debug|Any CPU
{9254C3EB-196B-402F-A059-34FEA6140500}.Debug|x86.ActiveCfg = Debug|Any CPU
{9254C3EB-196B-402F-A059-34FEA6140500}.Debug|x86.Build.0 = Debug|Any CPU
{9254C3EB-196B-402F-A059-34FEA6140500}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9254C3EB-196B-402F-A059-34FEA6140500}.Release|Any CPU.Build.0 = Release|Any CPU
{9254C3EB-196B-402F-A059-34FEA6140500}.Release|x64.ActiveCfg = Release|Any CPU
{9254C3EB-196B-402F-A059-34FEA6140500}.Release|x64.Build.0 = Release|Any CPU
{9254C3EB-196B-402F-A059-34FEA6140500}.Release|x86.ActiveCfg = Release|Any CPU
{9254C3EB-196B-402F-A059-34FEA6140500}.Release|x86.Build.0 = Release|Any CPU
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Debug|x64.ActiveCfg = Debug|Any CPU
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Debug|x64.Build.0 = Debug|Any CPU
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Debug|x86.ActiveCfg = Debug|Any CPU
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Debug|x86.Build.0 = Debug|Any CPU
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Release|Any CPU.Build.0 = Release|Any CPU
{FB9C6B61-0A7B-4FFA-B772-A754316B262E}.Release|x64.ActiveCfg = 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.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -405,6 +447,9 @@ Global
{A7994A41-CAF8-47A7-8975-F101F75B5BC1} = {8A3D00B8-1CCF-4BE6-A060-11104CE2D9CE}
{CE5523AE-6E38-4E20-998F-C64E02C5CC51} = {8A3D00B8-1CCF-4BE6-A060-11104CE2D9CE}
{7C24EAB8-57A9-4613-A8A6-4C21BB7D260D} = {A95C3BE1-B850-4265-97A0-777ADCCD437F}
{B5422347-E919-431D-9EF2-C352FFE4D6C1} = {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}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2D10D020-6770-47CA-BB8D-2C23FE3AE071}

View File

@ -90,5 +90,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
_handshakeTimeout = value != Timeout.InfiniteTimeSpan ? value : TimeSpan.MaxValue;
}
}
// For testing
internal Action OnHandshakeStarted;
}
}

View File

@ -127,6 +127,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
var timeoutFeature = context.Features.Get<IConnectionTimeoutFeature>();
timeoutFeature.SetTimeout(_options.HandshakeTimeout);
_options.OnHandshakeStarted?.Invoke();
try
{
#if NETCOREAPP2_1

View File

@ -21,6 +21,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public HttpConnectionManager ConnectionManager { get; set; }
public Heartbeat Heartbeat { get; set; }
public KestrelServerOptions ServerOptions { get; set; }
}
}

View File

@ -21,7 +21,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
public class KestrelServer : IServer
{
private readonly List<ITransport> _transports = new List<ITransport>();
private readonly Heartbeat _heartbeat;
private readonly IServerAddressesFeature _serverAddresses;
private readonly ITransportFactory _transportFactory;
@ -47,13 +46,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
_transportFactory = transportFactory;
ServiceContext = serviceContext;
var httpHeartbeatManager = new HttpHeartbeatManager(serviceContext.ConnectionManager);
_heartbeat = new Heartbeat(
new IHeartbeatHandler[] { serviceContext.DateHeaderValueManager, httpHeartbeatManager },
serviceContext.SystemClock,
DebuggerWrapper.Singleton,
Trace);
Features = new FeatureCollection();
_serverAddresses = new ServerAddressesFeature();
Features.Set(_serverAddresses);
@ -82,6 +74,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
var systemClock = new SystemClock();
var dateHeaderValueManager = new DateHeaderValueManager(systemClock);
var httpHeartbeatManager = new HttpHeartbeatManager(connectionManager);
var heartbeat = new Heartbeat(
new IHeartbeatHandler[] { dateHeaderValueManager, httpHeartbeatManager },
systemClock,
DebuggerWrapper.Singleton,
trace);
// TODO: This logic will eventually move into the IConnectionHandler<T> and off
// the service context once we get to https://github.com/aspnet/KestrelHttpServer/issues/1662
PipeScheduler scheduler = null;
@ -106,7 +105,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
SystemClock = systemClock,
DateHeaderValueManager = dateHeaderValueManager,
ConnectionManager = connectionManager,
ServerOptions = serverOptions
Heartbeat = heartbeat,
ServerOptions = serverOptions,
};
}
@ -137,7 +137,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted);
}
_hasStarted = true;
_heartbeat.Start();
ServiceContext.Heartbeat?.Start();
async Task OnBind(ListenOptions endpoint)
{
@ -203,7 +204,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
}
await Task.WhenAll(tasks).ConfigureAwait(false);
_heartbeat.Dispose();
ServiceContext.Heartbeat?.Dispose();
}
catch (Exception ex)
{

View File

@ -6,6 +6,9 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Libuv.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Sockets.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("InMemory.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Sockets.BindTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Libuv.BindTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Kestrel.Performance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -6,3 +6,6 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Sockets.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Libuv.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("InMemory.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Sockets.BindTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Libuv.BindTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -4,3 +4,4 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Libuv.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Libuv.BindTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -4,3 +4,4 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Sockets.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Sockets.BindTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -49,7 +49,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
public async Task OnConnectionCompletesTransportPipesAfterReturning()
{
var serviceContext = new TestServiceContext();
var tcs = new TaskCompletionSource<object>();
var dispatcher = new ConnectionDispatcher(serviceContext, _ => Task.CompletedTask);
var mockConnection = new Mock<TransportConnection>();

View File

@ -8,9 +8,9 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="..\shared\**\*.cs" />
<Compile Include="..\shared\*.cs" LinkBase="shared" />
<Content Include="..\shared\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
<Compile Include="..\..\shared\Microsoft.Extensions.Buffers.Testing.Sources\*.cs" />
<Content Include="..\shared\TestCertificates\*.pfx" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>

View File

@ -1,256 +0,0 @@
// 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.
#if !INNER_LOOP
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public class KeepAliveTimeoutTests : LoggedTest
{
private static readonly TimeSpan _keepAliveTimeout = TimeSpan.FromSeconds(10);
private static readonly TimeSpan _longDelay = TimeSpan.FromSeconds(30);
private static readonly TimeSpan _shortDelay = TimeSpan.FromSeconds(_longDelay.TotalSeconds / 10);
[Fact]
public Task TestKeepAliveTimeout()
{
// Delays in these tests cannot be much longer than expected.
// Call Task.Run() to get rid of Xunit's synchronization context,
// otherwise it can cause unexpectedly longer delays when multiple tests
// are running in parallel. These tests becomes flaky on slower
// hardware because the continuations for the delay tasks might take too long to be
// scheduled if running on Xunit's synchronization context.
return Task.Run(async () =>
{
var longRunningCancellationTokenSource = new CancellationTokenSource();
var upgradeCancellationTokenSource = new CancellationTokenSource();
using (var server = CreateServer(longRunningCancellationTokenSource.Token, upgradeCancellationTokenSource.Token))
{
var tasks = new[]
{
ConnectionClosedWhenKeepAliveTimeoutExpires(server),
ConnectionKeptAliveBetweenRequests(server),
ConnectionNotTimedOutWhileRequestBeingSent(server),
ConnectionNotTimedOutWhileAppIsRunning(server, longRunningCancellationTokenSource),
ConnectionTimesOutWhenOpenedButNoRequestSent(server),
KeepAliveTimeoutDoesNotApplyToUpgradedConnections(server, upgradeCancellationTokenSource)
};
await Task.WhenAll(tasks);
}
});
}
private async Task ConnectionClosedWhenKeepAliveTimeoutExpires(TestServer server)
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
await ReceiveResponse(connection);
await connection.WaitForConnectionClose().TimeoutAfter(_longDelay);
}
}
private async Task ConnectionKeptAliveBetweenRequests(TestServer server)
{
using (var connection = server.CreateConnection())
{
for (var i = 0; i < 10; i++)
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
// Don't change this to Task.Delay. See https://github.com/aspnet/KestrelHttpServer/issues/1684#issuecomment-330285740.
Thread.Sleep(_shortDelay);
}
for (var i = 0; i < 10; i++)
{
await ReceiveResponse(connection);
}
}
}
private async Task ConnectionNotTimedOutWhileRequestBeingSent(TestServer server)
{
using (var connection = server.CreateConnection())
{
var cts = new CancellationTokenSource();
cts.CancelAfter(_longDelay);
await connection.Send(
"POST /consume HTTP/1.1",
"Host:",
"Transfer-Encoding: chunked",
"",
"");
while (!cts.IsCancellationRequested)
{
await connection.Send(
"1",
"a",
"");
await Task.Delay(_shortDelay);
}
await connection.Send(
"0",
"",
"");
await ReceiveResponse(connection);
}
}
private async Task ConnectionNotTimedOutWhileAppIsRunning(TestServer server, CancellationTokenSource cts)
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET /longrunning HTTP/1.1",
"Host:",
"",
"");
cts.CancelAfter(_longDelay);
while (!cts.IsCancellationRequested)
{
await Task.Delay(1000);
}
await ReceiveResponse(connection);
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
await ReceiveResponse(connection);
}
}
private async Task ConnectionTimesOutWhenOpenedButNoRequestSent(TestServer server)
{
using (var connection = server.CreateConnection())
{
await Task.Delay(_longDelay);
await connection.WaitForConnectionClose().TimeoutAfter(_longDelay);
}
}
private async Task KeepAliveTimeoutDoesNotApplyToUpgradedConnections(TestServer server, CancellationTokenSource cts)
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET /upgrade HTTP/1.1",
"Host:",
"Connection: Upgrade",
"",
"");
await connection.Receive(
"HTTP/1.1 101 Switching Protocols",
"Connection: Upgrade",
"");
await connection.ReceiveStartsWith("Date: ");
await connection.Receive(
"",
"");
cts.CancelAfter(_longDelay);
while (!cts.IsCancellationRequested)
{
await Task.Delay(1000);
}
await connection.Receive("hello, world");
}
}
private TestServer CreateServer(CancellationToken longRunningCt, CancellationToken upgradeCt)
{
return new TestServer(httpContext => App(httpContext, longRunningCt, upgradeCt), new TestServiceContext(LoggerFactory)
{
// Use real SystemClock so timeouts trigger.
SystemClock = new SystemClock(),
ServerOptions =
{
AddServerHeader = false,
Limits =
{
KeepAliveTimeout = _keepAliveTimeout,
MinRequestBodyDataRate = null
}
}
});
}
private async Task App(HttpContext httpContext, CancellationToken longRunningCt, CancellationToken upgradeCt)
{
var ct = httpContext.RequestAborted;
var responseStream = httpContext.Response.Body;
var responseBytes = Encoding.ASCII.GetBytes("hello, world");
if (httpContext.Request.Path == "/longrunning")
{
while (!longRunningCt.IsCancellationRequested)
{
await Task.Delay(1000);
}
}
else if (httpContext.Request.Path == "/upgrade")
{
using (var stream = await httpContext.Features.Get<IHttpUpgradeFeature>().UpgradeAsync())
{
while (!upgradeCt.IsCancellationRequested)
{
await Task.Delay(_longDelay);
}
responseStream = stream;
}
}
else if (httpContext.Request.Path == "/consume")
{
var buffer = new byte[1024];
while (await httpContext.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) ;
}
await responseStream.WriteAsync(responseBytes, 0, responseBytes.Length);
}
private async Task ReceiveResponse(TestConnection connection)
{
await connection.Receive(
"HTTP/1.1 200 OK",
"");
await connection.ReceiveStartsWith("Date: ");
await connection.Receive(
"Transfer-Encoding: chunked",
"",
"c",
"hello, world",
"0",
"",
"");
}
}
}
#endif

View File

@ -1,53 +0,0 @@
// 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;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public class LoggingConnectionAdapterTests : LoggedTest
{
[Fact]
public async Task LoggingConnectionAdapterCanBeAddedBeforeAndAfterHttpsAdapter()
{
var host = TransportSelector.GetWebHostBuilder()
.ConfigureServices(AddTestLogging)
.UseKestrel(options =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
{
listenOptions.UseConnectionLogging();
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
listenOptions.UseConnectionLogging();
});
})
.Configure(app =>
{
app.Run(context =>
{
context.Response.ContentLength = 12;
return context.Response.WriteAsync("Hello World!");
});
})
.Build();
using (host)
{
await host.StartAsync();
var response = await HttpClientSlim.GetStringAsync($"https://localhost:{host.GetPort()}/", validateCertificate: false)
.DefaultTimeout();
Assert.Equal("Hello World!", response);
}
}
}
}

View File

@ -1,145 +0,0 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public class RequestHeadersTimeoutTests : LoggedTest
{
private static readonly TimeSpan RequestHeadersTimeout = TimeSpan.FromSeconds(10);
private static readonly TimeSpan LongDelay = TimeSpan.FromSeconds(30);
private static readonly TimeSpan ShortDelay = TimeSpan.FromSeconds(LongDelay.TotalSeconds / 10);
[Fact]
public async Task TestRequestHeadersTimeout()
{
using (var server = CreateServer())
{
var tasks = new[]
{
ConnectionAbortedWhenRequestHeadersNotReceivedInTime(server, "Host:\r\n"),
ConnectionAbortedWhenRequestHeadersNotReceivedInTime(server, "Host:\r\nContent-Length: 1\r\n"),
ConnectionAbortedWhenRequestHeadersNotReceivedInTime(server, "Host:\r\nContent-Length: 1\r\n\r"),
RequestHeadersTimeoutCanceledAfterHeadersReceived(server),
ConnectionAbortedWhenRequestLineNotReceivedInTime(server, "P"),
ConnectionAbortedWhenRequestLineNotReceivedInTime(server, "POST / HTTP/1.1\r"),
TimeoutNotResetOnEachRequestLineCharacterReceived(server)
};
await Task.WhenAll(tasks);
}
}
private async Task ConnectionAbortedWhenRequestHeadersNotReceivedInTime(TestServer server, string headers)
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
headers);
await ReceiveTimeoutResponse(connection);
}
}
private async Task RequestHeadersTimeoutCanceledAfterHeadersReceived(TestServer server)
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"POST / HTTP/1.1",
"Host:",
"Content-Length: 1",
"",
"");
await Task.Delay(RequestHeadersTimeout);
await connection.Send(
"a");
await ReceiveResponse(connection);
}
}
private async Task ConnectionAbortedWhenRequestLineNotReceivedInTime(TestServer server, string requestLine)
{
using (var connection = server.CreateConnection())
{
await connection.Send(requestLine);
await ReceiveTimeoutResponse(connection);
}
}
private async Task TimeoutNotResetOnEachRequestLineCharacterReceived(TestServer server)
{
using (var connection = server.CreateConnection())
{
await Assert.ThrowsAsync<IOException>(async () =>
{
foreach (var ch in "POST / HTTP/1.1\r\nHost:\r\n\r\n")
{
await connection.Send(ch.ToString());
await Task.Delay(ShortDelay);
}
});
}
}
private TestServer CreateServer()
{
return new TestServer(async httpContext =>
{
await httpContext.Request.Body.ReadAsync(new byte[1], 0, 1);
await httpContext.Response.WriteAsync("hello, world");
},
new TestServiceContext(LoggerFactory)
{
// Use real SystemClock so timeouts trigger.
SystemClock = new SystemClock(),
ServerOptions =
{
AddServerHeader = false,
Limits =
{
RequestHeadersTimeout = RequestHeadersTimeout,
MinRequestBodyDataRate = null
}
}
});
}
private async Task ReceiveResponse(TestConnection connection)
{
await connection.Receive(
"HTTP/1.1 200 OK",
"");
await connection.ReceiveStartsWith("Date: ");
await connection.Receive(
"Transfer-Encoding: chunked",
"",
"c",
"hello, world",
"0",
"",
"");
}
private async Task ReceiveTimeoutResponse(TestConnection connection)
{
await connection.Receive(
"HTTP/1.1 408 Request Timeout",
"Connection: close",
"");
await connection.ReceiveStartsWith("Date: ");
await connection.ReceiveForcedEnd(
"Content-Length: 0",
"",
"");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -6,14 +6,14 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class BadHttpRequestTests : LoggedTest
{
@ -158,7 +158,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await context.Request.Body.ReadAsync(new byte[1], 0, 1);
}, new TestServiceContext(LoggerFactory)))
{
using (var connection = new TestConnection(server.Port))
using (var connection = server.CreateConnection())
{
await connection.SendAll(
"GET ? HTTP/1.1",
@ -175,7 +175,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Fact]
public async Task TestRequestSplitting()
{
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory, Mock.Of<IKestrelTrace>())))
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory)))
{
using (var client = server.CreateConnection())
{
@ -191,7 +191,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
private async Task TestBadRequest(string request, string expectedResponseStatusCode, string expectedExceptionMessage, string expectedAllowHeader = null)
{
BadHttpRequestException loggedException = null;
var mockKestrelTrace = new Mock<IKestrelTrace>();
var mockKestrelTrace = new Mock<KestrelTrace>(Logger) { CallBase = true };
mockKestrelTrace
.Setup(trace => trace.IsEnabled(LogLevel.Information))
.Returns(true);
@ -212,7 +212,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
Assert.Equal(expectedExceptionMessage, loggedException.Message);
}
private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedResponseStatusCode, string expectedDateHeaderValue, string expectedAllowHeader = null)
private async Task ReceiveBadRequestResponse(InMemoryConnection connection, string expectedResponseStatusCode, string expectedDateHeaderValue, string expectedAllowHeader = null)
{
var lines = new[]
{

View File

@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class CertificateLoaderTests : LoggedTest
{

View File

@ -5,35 +5,20 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class ChunkedRequestTests : LoggedTest
{
public ChunkedRequestTests(ITestOutputHelper output) : base(output)
{
}
public static TheoryData<ListenOptions> ConnectionAdapterData => new TheoryData<ListenOptions>
{
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)),
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
{
ConnectionAdapters = { new PassThroughConnectionAdapter() }
}
};
private async Task App(HttpContext httpContext)
{
var request = httpContext.Request;
@ -62,13 +47,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await response.Body.WriteAsync(bytes, 0, bytes.Length);
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task Http10TransferEncoding(ListenOptions listenOptions)
[Fact]
public async Task Http10TransferEncoding()
{
var testContext = new TestServiceContext(LoggerFactory);
using (var server = new TestServer(App, testContext, listenOptions))
using (var server = new TestServer(App, testContext))
{
using (var connection = server.CreateConnection())
{
@ -92,13 +76,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task Http10KeepAliveTransferEncoding(ListenOptions listenOptions)
[Fact]
public async Task Http10KeepAliveTransferEncoding()
{
var testContext = new TestServiceContext();
using (var server = new TestServer(AppChunked, testContext, listenOptions))
using (var server = new TestServer(AppChunked, testContext))
{
using (var connection = server.CreateConnection())
{
@ -134,9 +117,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task RequestBodyIsConsumedAutomaticallyIfAppDoesntConsumeItFully(ListenOptions listenOptions)
[Fact]
public async Task RequestBodyIsConsumedAutomaticallyIfAppDoesntConsumeItFully()
{
var testContext = new TestServiceContext(LoggerFactory);
@ -150,7 +132,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
response.Headers["Content-Length"] = new[] { "11" };
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -189,9 +171,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task TrailingHeadersAreParsed(ListenOptions listenOptions)
[Fact]
public async Task TrailingHeadersAreParsed()
{
var requestCount = 10;
var requestsReceived = 0;
@ -222,7 +203,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
response.Headers["Content-Length"] = new[] { "11" };
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
}, new TestServiceContext(LoggerFactory), listenOptions))
}, new TestServiceContext(LoggerFactory)))
{
var response = string.Join("\r\n", new string[] {
"HTTP/1.1 200 OK",
@ -275,9 +256,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task TrailingHeadersCountTowardsHeadersTotalSizeLimit(ListenOptions listenOptions)
[Fact]
public async Task TrailingHeadersCountTowardsHeadersTotalSizeLimit()
{
const string transferEncodingHeaderLine = "Transfer-Encoding: chunked";
const string headerLine = "Header: value";
@ -293,7 +273,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
var buffer = new byte[128];
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) != 0) ; // read to end
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -320,9 +300,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task TrailingHeadersCountTowardsHeaderCountLimit(ListenOptions listenOptions)
[Fact]
public async Task TrailingHeadersCountTowardsHeaderCountLimit()
{
const string transferEncodingHeaderLine = "Transfer-Encoding: chunked";
const string headerLine = "Header: value";
@ -335,7 +314,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
var buffer = new byte[128];
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) != 0) ; // read to end
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -362,9 +341,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ExtensionsAreIgnored(ListenOptions listenOptions)
[Fact]
public async Task ExtensionsAreIgnored()
{
var testContext = new TestServiceContext(LoggerFactory);
var requestCount = 10;
@ -396,7 +374,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
response.Headers["Content-Length"] = new[] { "11" };
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
}, testContext, listenOptions))
}, testContext))
{
var response = string.Join("\r\n", new string[] {
"HTTP/1.1 200 OK",
@ -449,9 +427,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task InvalidLengthResultsIn400(ListenOptions listenOptions)
[Fact]
public async Task InvalidLengthResultsIn400()
{
var testContext = new TestServiceContext(LoggerFactory);
using (var server = new TestServer(async httpContext =>
@ -469,7 +446,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
response.Headers["Content-Length"] = new[] { "11" };
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -493,9 +470,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task InvalidSizedDataResultsIn400(ListenOptions listenOptions)
[Fact]
public async Task InvalidSizedDataResultsIn400()
{
var testContext = new TestServiceContext(LoggerFactory);
using (var server = new TestServer(async httpContext =>
@ -513,7 +489,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
response.Headers["Content-Length"] = new[] { "11" };
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -539,15 +515,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ChunkedNotFinalTransferCodingResultsIn400(ListenOptions listenOptions)
[Fact]
public async Task ChunkedNotFinalTransferCodingResultsIn400()
{
var testContext = new TestServiceContext(LoggerFactory);
using (var server = new TestServer(httpContext =>
{
return Task.CompletedTask;
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -643,9 +618,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ClosingConnectionMidChunkPrefixThrows(ListenOptions listenOptions)
[Fact]
public async Task ClosingConnectionMidChunkPrefixThrows()
{
var testContext = new TestServiceContext(LoggerFactory);
var readStartedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
@ -668,7 +642,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
exTcs.SetException(ex);
}
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -681,7 +655,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await readStartedTcs.Task.TimeoutAfter(TestConstants.DefaultTimeout);
connection.Socket.Shutdown(SocketShutdown.Send);
connection.ShutdownSend();
await connection.ReceiveEnd();

View File

@ -2,32 +2,21 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class ChunkedResponseTests : LoggedTest
{
public static TheoryData<ListenOptions> ConnectionAdapterData => new TheoryData<ListenOptions>
{
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)),
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
{
ConnectionAdapters = { new PassThroughConnectionAdapter() }
}
};
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ResponsesAreChunkedAutomatically(ListenOptions listenOptions)
[Fact]
public async Task ResponsesAreChunkedAutomatically()
{
var testContext = new TestServiceContext(LoggerFactory);
@ -36,7 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var response = httpContext.Response;
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello "), 0, 6);
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("World!"), 0, 6);
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -61,9 +50,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ResponsesAreNotChunkedAutomaticallyForHttp10Requests(ListenOptions listenOptions)
[Fact]
public async Task ResponsesAreNotChunkedAutomaticallyForHttp10Requests()
{
var testContext = new TestServiceContext(LoggerFactory);
@ -71,7 +59,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
await httpContext.Response.WriteAsync("Hello ");
await httpContext.Response.WriteAsync("World!");
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -90,9 +78,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ResponsesAreChunkedAutomaticallyForHttp11NonKeepAliveRequests(ListenOptions listenOptions)
[Fact]
public async Task ResponsesAreChunkedAutomaticallyForHttp11NonKeepAliveRequests()
{
var testContext = new TestServiceContext(LoggerFactory);
@ -100,7 +87,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
await httpContext.Response.WriteAsync("Hello ");
await httpContext.Response.WriteAsync("World!");
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -127,9 +114,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task SettingConnectionCloseHeaderInAppDoesNotDisableChunking(ListenOptions listenOptions)
[Fact]
public async Task SettingConnectionCloseHeaderInAppDoesNotDisableChunking()
{
var testContext = new TestServiceContext(LoggerFactory);
@ -138,7 +124,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
httpContext.Response.Headers["Connection"] = "close";
await httpContext.Response.WriteAsync("Hello ");
await httpContext.Response.WriteAsync("World!");
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -164,9 +150,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ZeroLengthWritesAreIgnored(ListenOptions listenOptions)
[Fact]
public async Task ZeroLengthWritesAreIgnored()
{
var testContext = new TestServiceContext(LoggerFactory);
@ -176,7 +161,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello "), 0, 6);
await response.Body.WriteAsync(new byte[0], 0, 0);
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("World!"), 0, 6);
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -201,9 +186,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ZeroLengthWritesFlushHeaders(ListenOptions listenOptions)
[Fact]
public async Task ZeroLengthWritesFlushHeaders()
{
var testContext = new TestServiceContext(LoggerFactory);
@ -217,7 +201,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await flushed.WaitAsync();
await response.WriteAsync("Hello World!");
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -246,9 +230,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task EmptyResponseBodyHandledCorrectlyWithZeroLengthWrite(ListenOptions listenOptions)
[Fact]
public async Task EmptyResponseBodyHandledCorrectlyWithZeroLengthWrite()
{
var testContext = new TestServiceContext(LoggerFactory);
@ -256,7 +239,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
var response = httpContext.Response;
await response.Body.WriteAsync(new byte[0], 0, 0);
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -277,9 +260,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ConnectionClosedIfExceptionThrownAfterWrite(ListenOptions listenOptions)
[Fact]
public async Task ConnectionClosedIfExceptionThrownAfterWrite()
{
var testContext = new TestServiceContext(LoggerFactory);
@ -288,7 +270,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var response = httpContext.Response;
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World!"), 0, 12);
throw new Exception();
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -311,9 +293,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ConnectionClosedIfExceptionThrownAfterZeroLengthWrite(ListenOptions listenOptions)
[Fact]
public async Task ConnectionClosedIfExceptionThrownAfterZeroLengthWrite()
{
var testContext = new TestServiceContext(LoggerFactory);
@ -322,7 +303,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var response = httpContext.Response;
await response.Body.WriteAsync(new byte[0], 0, 0);
throw new Exception();
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -344,9 +325,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task WritesAreFlushedPriorToResponseCompletion(ListenOptions listenOptions)
[Fact]
public async Task WritesAreFlushedPriorToResponseCompletion()
{
var testContext = new TestServiceContext(LoggerFactory);
@ -361,7 +341,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await flushWh.Task.DefaultTimeout();
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("World!"), 0, 6);
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{
@ -391,9 +371,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ChunksCanBeWrittenManually(ListenOptions listenOptions)
[Fact]
public async Task ChunksCanBeWrittenManually()
{
var testContext = new TestServiceContext(LoggerFactory);
@ -405,7 +384,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("6\r\nHello \r\n"), 0, 11);
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("6\r\nWorld!\r\n"), 0, 11);
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("0\r\n\r\n"), 0, 5);
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{

View File

@ -4,17 +4,17 @@
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class ConnectionAdapterTests : LoggedTest
{
@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var connection = server.CreateConnection())
{
// FIN
connection.Shutdown(SocketShutdown.Send);
connection.ShutdownSend();
await connection.WaitForConnectionClose();
}
}
@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var connection = server.CreateConnection())
{
// FIN
connection.Shutdown(SocketShutdown.Send);
connection.ShutdownSend();
await connection.WaitForConnectionClose();
}
}
@ -156,20 +156,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
using (var connection = server.CreateConnection())
{
// Will throw because the exception in the connection adapter will close the connection.
await Assert.ThrowsAsync<IOException>(async () =>
{
await connection.Send(
"POST / HTTP/1.0",
"Content-Length: 1000",
"\r\n");
await connection.Send(
"POST / HTTP/1.0",
"Content-Length: 1000",
"\r\n");
for (var i = 0; i < 1000; i++)
{
await connection.Send("a");
await Task.Delay(5);
}
});
await connection.WaitForConnectionClose();
}
}
}

View File

@ -10,13 +10,14 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Server.Kestrel.Tests;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class ConnectionLimitTests : LoggedTest
{
@ -62,7 +63,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
}, max: 1))
using (var disposables = new DisposableStack<TestConnection>())
using (var disposables = new DisposableStack<InMemoryConnection>())
{
var upgraded = server.CreateConnection();
disposables.Push(upgraded);
@ -87,7 +88,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
catch { }
// connection should close without sending any data
await rejected.WaitForConnectionClose().DefaultTimeout();
await rejected.WaitForConnectionClose();
}
}
}
@ -103,7 +104,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await context.Response.WriteAsync("Hello");
await requestTcs.Task;
}, max))
using (var disposables = new DisposableStack<TestConnection>())
using (var disposables = new DisposableStack<InMemoryConnection>())
{
for (var i = 0; i < max; i++)
{
@ -127,7 +128,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
catch { }
// connection should close without sending any data
await connection.WaitForConnectionClose().DefaultTimeout();
await connection.WaitForConnectionClose();
}
}

View File

@ -2,11 +2,12 @@
// 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.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class DefaultHeaderTests : LoggedTest
{

View File

@ -1,7 +1,6 @@
// 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.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
@ -9,11 +8,12 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class EventSourceTests : LoggedTest
{

View File

@ -1,11 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.IO;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
namespace System.IO.Pipelines
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.Http2
{
internal class PipeReaderFactory
{

View File

@ -6,7 +6,6 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Net;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
@ -17,12 +16,13 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.Http2
{
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")]
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81,
@ -41,31 +41,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
Assert.Equal(tlsFeature.ApplicationProtocol, SslApplicationProtocol.Http2.Protocol);
return context.Response.WriteAsync("hello world " + context.Request.Protocol);
}, new TestServiceContext(LoggerFactory),
kestrelOptions =>
},
new TestServiceContext(LoggerFactory),
listenOptions =>
{
kestrelOptions.Listen(IPAddress.Loopback, 0, listenOptions =>
listenOptions.Protocols = HttpProtocols.Http2;
listenOptions.UseHttps(_x509Certificate2, httpsOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
listenOptions.UseHttps(_x509Certificate2, httpsOptions =>
{
httpsOptions.SslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12;
});
httpsOptions.SslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12;
});
}))
{
var connection = server.CreateConnection();
var sslStream = new SslStream(connection.Stream);
await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions()
using (var connection = server.CreateConnection())
{
TargetHost = "localhost",
RemoteCertificateValidationCallback = (_, __, ___, ____) => true,
ApplicationProtocols = new List<SslApplicationProtocol>() { SslApplicationProtocol.Http2, SslApplicationProtocol.Http11 },
EnabledSslProtocols = SslProtocols.Tls11, // Intentionally less than the required 1.2
}, CancellationToken.None);
var sslStream = new SslStream(connection.Stream);
await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions()
{
TargetHost = "localhost",
RemoteCertificateValidationCallback = (_, __, ___, ____) => true,
ApplicationProtocols = new List<SslApplicationProtocol>() { SslApplicationProtocol.Http2, SslApplicationProtocol.Http11 },
EnabledSslProtocols = SslProtocols.Tls11, // Intentionally less than the required 1.2
}, CancellationToken.None);
var reader = PipeReaderFactory.CreateFromStream(PipeOptions.Default, sslStream, CancellationToken.None);
await WaitForConnectionErrorAsync(reader, ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.INADEQUATE_SECURITY);
var reader = PipeReaderFactory.CreateFromStream(PipeOptions.Default, sslStream, CancellationToken.None);
await WaitForConnectionErrorAsync(reader, ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.INADEQUATE_SECURITY);
reader.Complete();
}
}
}

View File

@ -5,7 +5,8 @@ using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging;
@ -13,7 +14,7 @@ using Microsoft.Extensions.Logging.Testing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class HttpConnectionManagerTests : LoggedTest
{
@ -31,7 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var logWh = new SemaphoreSlim(0);
var appStartedWh = new SemaphoreSlim(0);
var mockTrace = new Mock<IKestrelTrace>();
var mockTrace = new Mock<KestrelTrace>(Logger) { CallBase = true };
mockTrace
.Setup(trace => trace.ApplicationNeverCompleted(It.IsAny<string>()))
.Callback(() =>
@ -39,13 +40,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
logWh.Release();
});
var testContext = new TestServiceContext(new LoggerFactory(), mockTrace.Object);
testContext.InitializeHeartbeat();
using (var server = new TestServer(context =>
{
appStartedWh.Release();
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
return tcs.Task;
},
new TestServiceContext(new LoggerFactory(), mockTrace.Object)))
testContext))
{
using (var connection = server.CreateConnection())
{

View File

@ -5,16 +5,14 @@ using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class HttpProtocolSelectionTests : TestApplicationErrorLoggerLoggedTest
{
@ -45,22 +43,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
private async Task TestSuccess(HttpProtocols serverProtocols, string request, string expectedResponse)
{
var builder = TransportSelector.GetWebHostBuilder()
.ConfigureServices(AddTestLogging)
.UseKestrel(options =>
{
options.Listen(IPAddress.Loopback, 0, listenOptions =>
{
listenOptions.Protocols = serverProtocols;
});
})
.Configure(app => app.Run(context => Task.CompletedTask));
using (var host = builder.Build())
var testContext = new TestServiceContext(LoggerFactory);
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
{
host.Start();
Protocols = serverProtocols
};
using (var connection = new TestConnection(host.GetPort()))
using (var server = new TestServer(context => Task.CompletedTask, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
await connection.Send(request);
await connection.Receive(expectedResponse);
@ -71,21 +62,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
private async Task TestError<TException>(HttpProtocols serverProtocols, string expectedErrorMessage)
where TException : Exception
{
var builder = TransportSelector.GetWebHostBuilder()
.ConfigureServices(AddTestLogging)
.UseKestrel(options => options.Listen(IPAddress.Loopback, 0, listenOptions =>
{
listenOptions.Protocols = serverProtocols;
}))
.Configure(app => app.Run(context => Task.CompletedTask));
using (var host = builder.Build())
var testContext = new TestServiceContext(LoggerFactory);
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
{
host.Start();
Protocols = serverProtocols
};
using (var connection = new TestConnection(host.GetPort()))
using (var server = new TestServer(context => Task.CompletedTask, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
await connection.WaitForConnectionClose().DefaultTimeout();
await connection.WaitForConnectionClose();
}
}

View File

@ -8,11 +8,9 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http;
@ -20,11 +18,12 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class HttpsConnectionAdapterTests : LoggedTest
{
@ -44,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var server = new TestServer(App, new TestServiceContext(LoggerFactory), listenOptions))
{
var result = await HttpClientSlim.PostAsync($"https://localhost:{server.Port}/",
var result = await server.HttpClientSlim.PostAsync($"https://localhost:{server.Port}/",
new FormUrlEncodedContent(new[] {
new KeyValuePair<string, string>("content", "Hello World?")
}),
@ -80,7 +79,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
return context.Response.WriteAsync("hello world");
}, new TestServiceContext(LoggerFactory), listenOptions))
{
var result = await HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
Assert.Equal("hello world", result);
}
}
@ -104,7 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var server = new TestServer(App, new TestServiceContext(LoggerFactory), listenOptions))
{
await Assert.ThrowsAnyAsync<Exception>(
() => HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/"));
() => server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/"));
}
}
@ -131,7 +130,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
return context.Response.WriteAsync("hello world");
}, new TestServiceContext(LoggerFactory), listenOptions))
{
var result = await HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
Assert.Equal("hello world", result);
}
}
@ -157,12 +156,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions))
{
using (var client = new TcpClient())
using (var connection = server.CreateConnection())
{
// SslStream is used to ensure the certificate is actually passed to the server
// HttpClient might not send the certificate because it is invalid or it doesn't match any
// of the certificate authorities sent by the server in the SSL handshake.
var stream = await OpenSslStream(client, server);
var stream = OpenSslStream(connection.Stream);
await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2));
}
@ -198,12 +197,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
};
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions))
{
using (var client = new TcpClient())
using (var connection = server.CreateConnection())
{
// SslStream is used to ensure the certificate is actually passed to the server
// HttpClient might not send the certificate because it is invalid or it doesn't match any
// of the certificate authorities sent by the server in the SSL handshake.
var stream = await OpenSslStream(client, server);
var stream = OpenSslStream(connection.Stream);
await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2));
Assert.Equal(1, selectorCalled);
@ -244,22 +243,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
};
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions))
{
using (var client = new TcpClient())
using (var connection = server.CreateConnection())
{
// SslStream is used to ensure the certificate is actually passed to the server
// HttpClient might not send the certificate because it is invalid or it doesn't match any
// of the certificate authorities sent by the server in the SSL handshake.
var stream = await OpenSslStream(client, server);
var stream = OpenSslStream(connection.Stream);
await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2));
Assert.Equal(1, selectorCalled);
}
using (var client = new TcpClient())
using (var connection = server.CreateConnection())
{
// SslStream is used to ensure the certificate is actually passed to the server
// HttpClient might not send the certificate because it is invalid or it doesn't match any
// of the certificate authorities sent by the server in the SSL handshake.
var stream = await OpenSslStream(client, server);
var stream = OpenSslStream(connection.Stream);
await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2NoExt));
Assert.Equal(2, selectorCalled);
@ -287,12 +286,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
};
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions))
{
using (var client = new TcpClient())
using (var connection = server.CreateConnection())
{
// SslStream is used to ensure the certificate is actually passed to the server
// HttpClient might not send the certificate because it is invalid or it doesn't match any
// of the certificate authorities sent by the server in the SSL handshake.
var stream = await OpenSslStream(client, server);
var stream = OpenSslStream(connection.Stream);
await Assert.ThrowsAsync<IOException>(() =>
stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false));
Assert.Equal(1, selectorCalled);
@ -330,12 +329,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
};
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions))
{
using (var client = new TcpClient())
using (var connection = server.CreateConnection())
{
// SslStream is used to ensure the certificate is actually passed to the server
// HttpClient might not send the certificate because it is invalid or it doesn't match any
// of the certificate authorities sent by the server in the SSL handshake.
var stream = await OpenSslStream(client, server);
var stream = OpenSslStream(connection.Stream);
await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2));
Assert.Equal(1, selectorCalled);
@ -363,12 +362,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
};
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions))
{
using (var client = new TcpClient())
using (var connection = server.CreateConnection())
{
// SslStream is used to ensure the certificate is actually passed to the server
// HttpClient might not send the certificate because it is invalid or it doesn't match any
// of the certificate authorities sent by the server in the SSL handshake.
var stream = await OpenSslStream(client, server);
var stream = OpenSslStream(connection.Stream);
await Assert.ThrowsAsync<IOException>(() =>
stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false));
Assert.Equal(1, selectorCalled);
@ -404,12 +403,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
return context.Response.WriteAsync("hello world");
}, new TestServiceContext(LoggerFactory), listenOptions))
{
using (var client = new TcpClient())
using (var connection = server.CreateConnection())
{
// SslStream is used to ensure the certificate is actually passed to the server
// HttpClient might not send the certificate because it is invalid or it doesn't match any
// of the certificate authorities sent by the server in the SSL handshake.
var stream = await OpenSslStream(client, server);
var stream = OpenSslStream(connection.Stream);
await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
await AssertConnectionResult(stream, true);
}
@ -429,7 +428,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var server = new TestServer(context => context.Response.WriteAsync(context.Request.Scheme), new TestServiceContext(LoggerFactory), listenOptions))
{
var result = await HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
Assert.Equal("https", result);
}
}
@ -455,9 +454,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
// SslStream is used to ensure the certificate is actually passed to the server
// HttpClient might not send the certificate because it is invalid or it doesn't match any
// of the certificate authorities sent by the server in the SSL handshake.
using (var client = new TcpClient())
using (var connection = server.CreateConnection())
{
var stream = await OpenSslStream(client, server);
var stream = OpenSslStream(connection.Stream);
var ex = await Assert.ThrowsAsync<IOException>(
async () => await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls, false));
}
@ -491,9 +490,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions))
{
using (var client = new TcpClient())
using (var connection = server.CreateConnection())
{
var stream = await OpenSslStream(client, server);
var stream = OpenSslStream(connection.Stream);
await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
await AssertConnectionResult(stream, true);
Assert.True(clientCertificateValidationCalled);
@ -521,9 +520,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions))
{
using (var client = new TcpClient())
using (var connection = server.CreateConnection())
{
var stream = await OpenSslStream(client, server);
var stream = OpenSslStream(connection.Stream);
await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
await AssertConnectionResult(stream, false);
}
@ -549,9 +548,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions))
{
using (var client = new TcpClient())
using (var connection = server.CreateConnection())
{
var stream = await OpenSslStream(client, server);
var stream = OpenSslStream(connection.Stream);
await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
await AssertConnectionResult(stream, false);
}
@ -589,9 +588,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
// SslStream is used to ensure the certificate is actually passed to the server
// HttpClient might not send the certificate because it is invalid or it doesn't match any
// of the certificate authorities sent by the server in the SSL handshake.
using (var client = new TcpClient())
using (var connection = server.CreateConnection())
{
var stream = await OpenSslStream(client, server);
var stream = OpenSslStream(connection.Stream);
await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
await AssertConnectionResult(stream, true);
}
@ -668,13 +667,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
private static async Task<SslStream> OpenSslStream(TcpClient client, TestServer server, X509Certificate2 clientCertificate = null)
private static SslStream OpenSslStream(Stream rawStream, X509Certificate2 clientCertificate = null)
{
await client.ConnectAsync("127.0.0.1", server.Port);
var stream = new SslStream(client.GetStream(), false, (sender, certificate, chain, errors) => true,
return new SslStream(rawStream, false, (sender, certificate, chain, errors) => true,
(sender, host, certificates, certificate, issuers) => clientCertificate ?? _x509Certificate2);
return stream;
}
private static async Task AssertConnectionResult(SslStream stream, bool success)

View File

@ -4,19 +4,18 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@ -24,7 +23,7 @@ using Microsoft.Extensions.Logging.Abstractions.Internal;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class HttpsTests : LoggedTest
{
@ -121,23 +120,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var loggerProvider = new HandshakeErrorLoggerProvider();
LoggerFactory.AddProvider(loggerProvider);
var hostBuilder = TransportSelector.GetWebHostBuilder()
.UseKestrel(options =>
using (var server = new TestServer(context => Task.CompletedTask,
new TestServiceContext(LoggerFactory),
listenOptions =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
{
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
});
})
.ConfigureServices(AddTestLogging)
.ConfigureLogging(builder => builder.AddProvider(loggerProvider))
.Configure(app => { });
using (var host = hostBuilder.Build())
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
}))
{
host.Start();
using (await HttpClientSlim.GetSocket(new Uri($"http://127.0.0.1:{host.GetPort()}/")))
using (var connection = server.CreateConnection())
{
// Close socket immediately
}
@ -157,26 +147,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var loggerProvider = new HandshakeErrorLoggerProvider();
LoggerFactory.AddProvider(loggerProvider);
var hostBuilder = TransportSelector.GetWebHostBuilder()
.UseKestrel(options =>
using (var server = new TestServer(context => Task.CompletedTask,
new TestServiceContext(LoggerFactory),
listenOptions =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
{
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
});
})
.ConfigureServices(AddTestLogging)
.Configure(app => { });
using (var host = hostBuilder.Build())
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
}))
{
host.Start();
using (var socket = await HttpClientSlim.GetSocket(new Uri($"https://127.0.0.1:{host.GetPort()}/")))
using (var stream = new NetworkStream(socket))
using (var connection = server.CreateConnection())
{
// Send null bytes and close socket
await stream.WriteAsync(new byte[10], 0, 10);
await connection.Stream.WriteAsync(new byte[10], 0, 10);
}
await loggerProvider.FilterLogger.LogTcs.Task.DefaultTimeout();
@ -194,17 +175,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
var loggerProvider = new HandshakeErrorLoggerProvider();
LoggerFactory.AddProvider(loggerProvider);
var hostBuilder = TransportSelector.GetWebHostBuilder()
.UseKestrel(options =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
{
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
});
})
.ConfigureServices(AddTestLogging)
.ConfigureLogging(builder => builder.AddProvider(loggerProvider))
.Configure(app => app.Run(async httpContext =>
using (var server = new TestServer(async httpContext =>
{
var ct = httpContext.RequestAborted;
while (!ct.IsCancellationRequested)
@ -219,15 +191,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
// Don't regard connection abort as an error
}
}
}));
using (var host = hostBuilder.Build())
},
new TestServiceContext(LoggerFactory),
listenOptions =>
{
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
}))
{
host.Start();
using (var socket = await HttpClientSlim.GetSocket(new Uri($"https://127.0.0.1:{host.GetPort()}/")))
using (var stream = new NetworkStream(socket, ownsSocket: false))
using (var sslStream = new SslStream(stream, true, (sender, certificate, chain, errors) => true))
using (var connection = server.CreateConnection())
using (var sslStream = new SslStream(connection.Stream, true, (sender, certificate, chain, errors) => true))
{
await sslStream.AuthenticateAsClientAsync("127.0.0.1", clientCertificates: null,
enabledSslProtocols: SslProtocols.Tls11 | SslProtocols.Tls12,
@ -249,17 +221,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var loggerProvider = new HandshakeErrorLoggerProvider();
LoggerFactory.AddProvider(loggerProvider);
var hostBuilder = TransportSelector.GetWebHostBuilder()
.UseKestrel(options =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
{
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
});
})
.ConfigureServices(AddTestLogging)
.ConfigureLogging(builder => builder.AddProvider(loggerProvider))
.Configure(app => app.Run(async httpContext =>
using (var server = new TestServer(async httpContext =>
{
httpContext.Abort();
try
@ -271,15 +234,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
tcs.SetException(ex);
}
}));
using (var host = hostBuilder.Build())
},
new TestServiceContext(LoggerFactory),
listenOptions =>
{
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
}))
{
host.Start();
using (var socket = await HttpClientSlim.GetSocket(new Uri($"https://127.0.0.1:{host.GetPort()}/")))
using (var stream = new NetworkStream(socket, ownsSocket: false))
using (var sslStream = new SslStream(stream, true, (sender, certificate, chain, errors) => true))
using (var connection = server.CreateConnection())
using (var sslStream = new SslStream(connection.Stream, true, (sender, certificate, chain, errors) => true))
{
await sslStream.AuthenticateAsClientAsync("127.0.0.1", clientCertificates: null,
enabledSslProtocols: SslProtocols.Tls11 | SslProtocols.Tls12,
@ -290,9 +253,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await sslStream.ReadAsync(new byte[32], 0, 32);
}
}
await tcs.Task.DefaultTimeout();
await tcs.Task.DefaultTimeout();
}
}
// Regression test for https://github.com/aspnet/KestrelHttpServer/issues/1693
@ -301,25 +264,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
var loggerProvider = new HandshakeErrorLoggerProvider();
LoggerFactory.AddProvider(loggerProvider);
var hostBuilder = TransportSelector.GetWebHostBuilder()
.UseKestrel(options =>
using (var server = new TestServer(context => Task.CompletedTask,
new TestServiceContext(LoggerFactory),
listenOptions =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
{
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
});
})
.ConfigureServices(AddTestLogging)
.ConfigureLogging(builder => builder.AddProvider(loggerProvider))
.Configure(app => app.Run(httpContext => Task.CompletedTask));
using (var host = hostBuilder.Build())
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
}))
{
host.Start();
using (var socket = await HttpClientSlim.GetSocket(new Uri($"https://127.0.0.1:{host.GetPort()}/")))
using (var stream = new NetworkStream(socket, ownsSocket: false))
using (var sslStream = new SslStream(stream, true, (sender, certificate, chain, errors) => true))
using (var connection = server.CreateConnection())
using (var sslStream = new SslStream(connection.Stream, true, (sender, certificate, chain, errors) => true))
{
await sslStream.AuthenticateAsClientAsync("127.0.0.1", clientCertificates: null,
enabledSslProtocols: SslProtocols.Tls11 | SslProtocols.Tls12,
@ -337,28 +291,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var loggerProvider = new HandshakeErrorLoggerProvider();
LoggerFactory.AddProvider(loggerProvider);
var hostBuilder = TransportSelector.GetWebHostBuilder()
.UseKestrel(options =>
using (var server = new TestServer(context => Task.CompletedTask,
new TestServiceContext(LoggerFactory),
listenOptions =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
{
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
});
})
.ConfigureServices(AddTestLogging)
.ConfigureLogging(builder => builder.AddProvider(loggerProvider))
.Configure(app => { });
using (var host = hostBuilder.Build())
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
}))
{
host.Start();
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
using (var connection = server.CreateConnection())
{
socket.Connect(new IPEndPoint(IPAddress.Loopback, host.GetPort()));
// Close socket immediately
socket.LingerState = new LingerOption(true, 0);
connection.Reset();
}
}
}
@ -368,30 +310,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
var loggerProvider = new HandshakeErrorLoggerProvider();
LoggerFactory.AddProvider(loggerProvider);
var hostBuilder = TransportSelector.GetWebHostBuilder()
.UseKestrel(options =>
var testContext = new TestServiceContext(LoggerFactory);
var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager);
var handshakeStartedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
TimeSpan handshakeTimeout = default;
using (var server = new TestServer(context => Task.CompletedTask,
testContext,
listenOptions =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
listenOptions.UseHttps(o =>
{
listenOptions.UseHttps(o =>
{
o.ServerCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
o.HandshakeTimeout = TimeSpan.FromSeconds(1);
});
o.ServerCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
o.OnHandshakeStarted = () => handshakeStartedTcs.SetResult(null);
handshakeTimeout = o.HandshakeTimeout;
});
})
.ConfigureServices(AddTestLogging)
.Configure(app => app.Run(httpContext => Task.CompletedTask));
using (var host = hostBuilder.Build())
}))
{
host.Start();
using (var socket = await HttpClientSlim.GetSocket(new Uri($"https://127.0.0.1:{host.GetPort()}/")))
using (var stream = new NetworkStream(socket, ownsSocket: false))
using (var connection = server.CreateConnection())
{
// No data should be sent and the connection should be closed in well under 30 seconds.
Assert.Equal(0, await stream.ReadAsync(new byte[1], 0, 1).DefaultTimeout());
// HttpsConnectionAdapter dispatches via Task.Run() before starting the handshake.
// Wait for the handshake to start before advancing the system clock.
await handshakeStartedTcs.Task.DefaultTimeout();
// Min amount of time between requests that triggers a handshake timeout.
testContext.MockSystemClock.UtcNow += handshakeTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1);
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
Assert.Equal(0, await connection.Stream.ReadAsync(new byte[1], 0, 1).DefaultTimeout());
}
}
@ -405,24 +354,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
var loggerProvider = new HandshakeErrorLoggerProvider();
LoggerFactory.AddProvider(loggerProvider);
var hostBuilder = TransportSelector.GetWebHostBuilder()
.UseKestrel(options =>
using (var server = new TestServer(context => Task.CompletedTask,
new TestServiceContext(LoggerFactory),
listenOptions =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
{
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
});
})
.ConfigureServices(AddTestLogging)
.Configure(app => app.Run(httpContext => Task.CompletedTask));
using (var host = hostBuilder.Build())
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
}))
{
host.Start();
using (var socket = await HttpClientSlim.GetSocket(new Uri($"https://127.0.0.1:{host.GetPort()}/")))
using (var stream = new NetworkStream(socket, ownsSocket: false))
using (var sslStream = new SslStream(stream, true, (sender, certificate, chain, errors) => true))
using (var connection = server.CreateConnection())
using (var sslStream = new SslStream(connection.Stream, true, (sender, certificate, chain, errors) => true))
{
// SslProtocols.Tls is TLS 1.0 which isn't supported by Kestrel by default.
await Assert.ThrowsAsync<IOException>(() =>

View File

@ -0,0 +1,266 @@
// 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.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class KeepAliveTimeoutTests : LoggedTest
{
private static readonly TimeSpan _keepAliveTimeout = TimeSpan.FromSeconds(10);
private static readonly TimeSpan _longDelay = TimeSpan.FromSeconds(30);
private static readonly TimeSpan _shortDelay = TimeSpan.FromSeconds(_longDelay.TotalSeconds / 10);
private readonly TaskCompletionSource<object> _firstRequestReceived = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
[Fact]
public async Task ConnectionClosedWhenKeepAliveTimeoutExpires()
{
var testContext = new TestServiceContext(LoggerFactory);
var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager);
using (var server = CreateServer(testContext))
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
await ReceiveResponse(connection, testContext);
// Min amount of time between requests that triggers a keep-alive timeout.
testContext.MockSystemClock.UtcNow += _keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1);
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
await connection.WaitForConnectionClose();
}
}
[Fact]
public async Task ConnectionKeptAliveBetweenRequests()
{
var testContext = new TestServiceContext(LoggerFactory);
var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager);
using (var server = CreateServer(testContext))
using (var connection = server.CreateConnection())
{
for (var i = 0; i < 10; i++)
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
await ReceiveResponse(connection, testContext);
// Max amount of time between requests that doesn't trigger a keep-alive timeout.
testContext.MockSystemClock.UtcNow += _keepAliveTimeout + Heartbeat.Interval;
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
}
}
}
[Fact]
public async Task ConnectionNotTimedOutWhileRequestBeingSent()
{
var testContext = new TestServiceContext(LoggerFactory);
var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager);
using (var server = CreateServer(testContext))
using (var connection = server.CreateConnection())
{
await connection.Send(
"POST /consume HTTP/1.1",
"Host:",
"Transfer-Encoding: chunked",
"",
"");
await _firstRequestReceived.Task.DefaultTimeout();
for (var totalDelay = TimeSpan.Zero; totalDelay < _longDelay; totalDelay += _shortDelay)
{
await connection.Send(
"1",
"a",
"");
testContext.MockSystemClock.UtcNow += _shortDelay;
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
}
await connection.Send(
"0",
"",
"");
await ReceiveResponse(connection, testContext);
}
}
[Fact]
private async Task ConnectionNotTimedOutWhileAppIsRunning()
{
var testContext = new TestServiceContext(LoggerFactory);
var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager);
var cts = new CancellationTokenSource();
using (var server = CreateServer(testContext, longRunningCt: cts.Token))
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET /longrunning HTTP/1.1",
"Host:",
"",
"");
await _firstRequestReceived.Task.DefaultTimeout();
for (var totalDelay = TimeSpan.Zero; totalDelay < _longDelay; totalDelay += _shortDelay)
{
testContext.MockSystemClock.UtcNow += _shortDelay;
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
}
cts.Cancel();
await ReceiveResponse(connection, testContext);
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
await ReceiveResponse(connection, testContext);
}
}
[Fact]
private async Task ConnectionTimesOutWhenOpenedButNoRequestSent()
{
var testContext = new TestServiceContext(LoggerFactory);
var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager);
using (var server = CreateServer(testContext))
using (var connection = server.CreateConnection())
{
// Min amount of time between requests that triggers a keep-alive timeout.
testContext.MockSystemClock.UtcNow += _keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1);
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
await connection.WaitForConnectionClose();
}
}
[Fact]
private async Task KeepAliveTimeoutDoesNotApplyToUpgradedConnections()
{
var testContext = new TestServiceContext(LoggerFactory);
var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager);
var cts = new CancellationTokenSource();
using (var server = CreateServer(testContext, upgradeCt: cts.Token))
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET /upgrade HTTP/1.1",
"Host:",
"Connection: Upgrade",
"",
"");
await connection.Receive(
"HTTP/1.1 101 Switching Protocols",
"Connection: Upgrade",
$"Date: {testContext.DateHeaderValue}",
"",
"");
for (var totalDelay = TimeSpan.Zero; totalDelay < _longDelay; totalDelay += _shortDelay)
{
testContext.MockSystemClock.UtcNow += _shortDelay;
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
}
cts.Cancel();
await connection.Receive("hello, world");
}
}
private TestServer CreateServer(TestServiceContext context, CancellationToken longRunningCt = default, CancellationToken upgradeCt = default)
{
context.ServerOptions.AddServerHeader = false;
context.ServerOptions.Limits.KeepAliveTimeout = _keepAliveTimeout;
context.ServerOptions.Limits.MinRequestBodyDataRate = null;
return new TestServer(httpContext => App(httpContext, longRunningCt, upgradeCt), context);
}
private async Task App(HttpContext httpContext, CancellationToken longRunningCt, CancellationToken upgradeCt)
{
var ct = httpContext.RequestAborted;
var responseStream = httpContext.Response.Body;
var responseBytes = Encoding.ASCII.GetBytes("hello, world");
_firstRequestReceived.TrySetResult(null);
if (httpContext.Request.Path == "/longrunning")
{
await CancellationTokenAsTask(longRunningCt);
}
else if (httpContext.Request.Path == "/upgrade")
{
using (var stream = await httpContext.Features.Get<IHttpUpgradeFeature>().UpgradeAsync())
{
await CancellationTokenAsTask(upgradeCt);
responseStream = stream;
}
}
else if (httpContext.Request.Path == "/consume")
{
var buffer = new byte[1024];
while (await httpContext.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) ;
}
await responseStream.WriteAsync(responseBytes, 0, responseBytes.Length);
}
private async Task ReceiveResponse(InMemoryConnection connection, TestServiceContext testContext)
{
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {testContext.DateHeaderValue}",
"Transfer-Encoding: chunked",
"",
"c",
"hello, world",
"0",
"",
"");
}
private static Task CancellationTokenAsTask(CancellationToken token)
{
if (token.IsCancellationRequested)
{
return Task.CompletedTask;
}
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
token.Register(() => tcs.SetResult(null));
return tcs.Task;
}
}
}

View File

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>InMemory.FunctionalTests</AssemblyName>
<RootNamespace>Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests</RootNamespace>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\shared\*.cs" LinkBase="shared" />
<Compile Include="..\shared\FunctionalTestHelpers\*.cs" LinkBase="shared\FunctionalTestHelpers" />
<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.Http.Abstractions" Version="$(MicrosoftAspNetCoreHttpAbstractionsPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,40 @@
// 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.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class LoggingConnectionAdapterTests : LoggedTest
{
[Fact]
public async Task LoggingConnectionAdapterCanBeAddedBeforeAndAfterHttpsAdapter()
{
using (var server = new TestServer(context =>
{
context.Response.ContentLength = 12;
return context.Response.WriteAsync("Hello World!");
},
new TestServiceContext(LoggerFactory),
listenOptions =>
{
listenOptions.UseConnectionLogging();
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
listenOptions.UseConnectionLogging();
}))
{
var response = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false)
.DefaultTimeout();
Assert.Equal("Hello World!", response);
}
}
}
}

View File

@ -7,11 +7,12 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class MaxRequestBodySizeTests : LoggedTest
{

View File

@ -4,11 +4,12 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class MaxRequestLineSizeTests : LoggedTest
{
@ -29,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
using (var server = CreateServer(limit))
{
using (var connection = new TestConnection(server.Port))
using (var connection = server.CreateConnection())
{
await connection.Send(request);
await connection.ReceiveEnd(
@ -55,7 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
using (var server = CreateServer(requestLine.Length - 1))
{
using (var connection = new TestConnection(server.Port))
using (var connection = server.CreateConnection())
{
await connection.SendAll(requestLine);
await connection.ReceiveForcedEnd(

View File

@ -0,0 +1,6 @@
// 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 Microsoft.Extensions.Logging.Testing;
[assembly: ShortClassName]

View File

@ -2,19 +2,18 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class RequestBodyTimeoutTests : LoggedTest
{
@ -22,12 +21,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public async Task RequestTimesOutWhenRequestBodyNotReceivedAtSpecifiedMinimumRate()
{
var gracePeriod = TimeSpan.FromSeconds(5);
var systemClock = new MockSystemClock();
var serviceContext = new TestServiceContext(LoggerFactory)
{
SystemClock = systemClock,
DateHeaderValueManager = new DateHeaderValueManager(systemClock)
};
var serviceContext = new TestServiceContext(LoggerFactory);
var heartbeatManager = new HttpHeartbeatManager(serviceContext.ConnectionManager);
var appRunningEvent = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
@ -74,7 +69,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
"");
await appRunningEvent.Task.DefaultTimeout();
systemClock.UtcNow += gracePeriod + TimeSpan.FromSeconds(1);
serviceContext.MockSystemClock.UtcNow += gracePeriod + TimeSpan.FromSeconds(1);
heartbeatManager.OnHeartbeat(serviceContext.SystemClock.UtcNow);
await connection.Receive(
"HTTP/1.1 408 Request Timeout",
@ -93,12 +90,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public async Task RequestTimesOutWhenNotDrainedWithinDrainTimeoutPeriod()
{
// This test requires a real clock since we can't control when the drain timeout is set
var systemClock = new SystemClock();
var serviceContext = new TestServiceContext(LoggerFactory)
{
SystemClock = systemClock,
DateHeaderValueManager = new DateHeaderValueManager(systemClock),
};
var serviceContext = new TestServiceContext(LoggerFactory);
serviceContext.InitializeHeartbeat();
var appRunningEvent = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
@ -142,12 +135,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public async Task ConnectionClosedEvenIfAppSwallowsException()
{
var gracePeriod = TimeSpan.FromSeconds(5);
var systemClock = new MockSystemClock();
var serviceContext = new TestServiceContext(LoggerFactory)
{
SystemClock = systemClock,
DateHeaderValueManager = new DateHeaderValueManager(systemClock)
};
var serviceContext = new TestServiceContext(LoggerFactory);
var heartbeatManager = new HttpHeartbeatManager(serviceContext.ConnectionManager);
var appRunningTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var exceptionSwallowedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
@ -190,7 +179,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
"");
await appRunningTcs.Task.DefaultTimeout();
systemClock.UtcNow += gracePeriod + TimeSpan.FromSeconds(1);
serviceContext.MockSystemClock.UtcNow += gracePeriod + TimeSpan.FromSeconds(1);
heartbeatManager.OnHeartbeat(serviceContext.SystemClock.UtcNow);
await exceptionSwallowedTcs.Task.DefaultTimeout();
await connection.Receive(

View File

@ -5,11 +5,12 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class RequestHeaderLimitsTests : LoggedTest
{
@ -28,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var server = CreateServer(maxRequestHeadersTotalSize: headers.Length + extraLimit))
{
using (var connection = new TestConnection(server.Port))
using (var connection = server.CreateConnection())
{
await connection.Send($"GET / HTTP/1.1\r\n{headers}\r\n");
await connection.ReceiveEnd(
@ -60,7 +61,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var server = CreateServer(maxRequestHeaderCount: maxHeaderCount))
{
using (var connection = new TestConnection(server.Port))
using (var connection = server.CreateConnection())
{
await connection.Send($"GET / HTTP/1.1\r\n{headers}\r\n");
await connection.ReceiveEnd(
@ -86,7 +87,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var server = CreateServer(maxRequestHeadersTotalSize: headers.Length - 1))
{
using (var connection = new TestConnection(server.Port))
using (var connection = server.CreateConnection())
{
await connection.SendAll($"GET / HTTP/1.1\r\n{headers}\r\n");
await connection.ReceiveForcedEnd(
@ -110,7 +111,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var server = CreateServer(maxRequestHeaderCount: maxHeaderCount))
{
using (var connection = new TestConnection(server.Port))
using (var connection = server.CreateConnection())
{
await connection.SendAll($"GET / HTTP/1.1\r\n{headers}\r\n");
await connection.ReceiveForcedEnd(

View File

@ -0,0 +1,159 @@
// 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.Pipelines;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class RequestHeadersTimeoutTests : LoggedTest
{
private static readonly TimeSpan RequestHeadersTimeout = TimeSpan.FromSeconds(10);
private static readonly TimeSpan LongDelay = TimeSpan.FromSeconds(30);
private static readonly TimeSpan ShortDelay = TimeSpan.FromSeconds(LongDelay.TotalSeconds / 10);
[Theory]
[InlineData("Host:\r\n")]
[InlineData("Host:\r\nContent-Length: 1\r\n")]
[InlineData("Host:\r\nContent-Length: 1\r\n\r")]
public async Task ConnectionAbortedWhenRequestHeadersNotReceivedInTime(string headers)
{
var testContext = new TestServiceContext(LoggerFactory);
var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager);
using (var server = CreateServer(testContext))
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
headers);
// Min amount of time between requests that triggers a request headers timeout.
testContext.MockSystemClock.UtcNow += RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1);
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
await ReceiveTimeoutResponse(connection, testContext);
}
}
[Fact]
public async Task RequestHeadersTimeoutCanceledAfterHeadersReceived()
{
var testContext = new TestServiceContext(LoggerFactory);
var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager);
using (var server = CreateServer(testContext))
using (var connection = server.CreateConnection())
{
await connection.Send(
"POST / HTTP/1.1",
"Host:",
"Content-Length: 1",
"",
"");
// Min amount of time between requests that triggers a request headers timeout.
testContext.MockSystemClock.UtcNow += RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1);
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
await connection.Send(
"a");
await ReceiveResponse(connection, testContext);
}
}
[Theory]
[InlineData("P")]
[InlineData("POST / HTTP/1.1\r")]
public async Task ConnectionAbortedWhenRequestLineNotReceivedInTime(string requestLine)
{
var testContext = new TestServiceContext(LoggerFactory);
var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager);
using (var server = CreateServer(testContext))
using (var connection = server.CreateConnection())
{
await connection.Send(requestLine);
// Min amount of time between requests that triggers a request headers timeout.
testContext.MockSystemClock.UtcNow += RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1);
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
await ReceiveTimeoutResponse(connection, testContext);
}
}
[Fact]
public async Task TimeoutNotResetOnEachRequestLineCharacterReceived()
{
var testContext = new TestServiceContext(LoggerFactory);
var heartbeatManager = new HttpHeartbeatManager(testContext.ConnectionManager);
using (var server = CreateServer(testContext))
using (var connection = server.CreateConnection())
{
// When the in-memory connection is aborted, the input PipeWriter is completed behind the scenes
// so eventually connection.Send() throws an InvalidOperationException.
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
{
foreach (var ch in "POST / HTTP/1.1\r\nHost:\r\n\r\n")
{
await connection.Send(ch.ToString());
testContext.MockSystemClock.UtcNow += ShortDelay;
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
}
});
await ReceiveTimeoutResponse(connection, testContext);
}
}
private TestServer CreateServer(TestServiceContext context)
{
// Ensure request headers timeout is started as soon as the tests send requests.
context.Scheduler = PipeScheduler.Inline;
context.ServerOptions.Limits.RequestHeadersTimeout = RequestHeadersTimeout;
context.ServerOptions.Limits.MinRequestBodyDataRate = null;
return new TestServer(async httpContext =>
{
await httpContext.Request.Body.ReadAsync(new byte[1], 0, 1);
await httpContext.Response.WriteAsync("hello, world");
}, context);
}
private async Task ReceiveResponse(InMemoryConnection connection, TestServiceContext testContext)
{
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {testContext.DateHeaderValue}",
"Transfer-Encoding: chunked",
"",
"c",
"hello, world",
"0",
"",
"");
}
private async Task ReceiveTimeoutResponse(InMemoryConnection connection, TestServiceContext testContext)
{
await connection.Receive(
"HTTP/1.1 408 Request Timeout",
"Connection: close",
$"Date: {testContext.DateHeaderValue}",
"Content-Length: 0",
"",
"");
}
}
}

View File

@ -1,18 +1,17 @@
// 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.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class RequestTargetProcessingTests : LoggedTest
{
@ -20,7 +19,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public async Task RequestPathIsNotNormalized()
{
var testContext = new TestServiceContext(LoggerFactory);
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
using (var server = new TestServer(async context =>
{
@ -28,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
context.Response.Headers.ContentLength = 11;
await context.Response.WriteAsync("Hello World");
}, testContext, listenOptions))
}, testContext))
{
using (var connection = server.CreateConnection())
{

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
// 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 Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Testing;
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport
{
public class InMemoryConnection : StreamBackedTestConnection
{
private readonly InMemoryTransportConnection _transportConnection;
public InMemoryConnection(InMemoryTransportConnection transportConnection)
: base(new RawStream(transportConnection.Output, transportConnection.Input))
{
_transportConnection = transportConnection;
}
public override void Reset()
{
_transportConnection.Input.Complete(new ConnectionResetException(string.Empty));
_transportConnection.OnClosed();
}
public override void ShutdownSend()
{
_transportConnection.Input.Complete();
_transportConnection.OnClosed();
}
public override void Dispose()
{
_transportConnection.Input.Complete();
_transportConnection.Output.Complete();
_transportConnection.OnClosed();
base.Dispose();
}
}
}

View File

@ -0,0 +1,137 @@
// 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.Globalization;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport
{
/// <summary>
/// Lightweight version of HttpClient implemented on top of an arbitrary Stream.
/// </summary>
public class InMemoryHttpClientSlim
{
private readonly TestServer _inMemoryTestServer;
public InMemoryHttpClientSlim(TestServer testServer)
{
_inMemoryTestServer = testServer;
}
public async Task<string> GetStringAsync(string requestUri, bool validateCertificate = true)
=> await GetStringAsync(new Uri(requestUri), validateCertificate).ConfigureAwait(false);
public async Task<string> GetStringAsync(Uri requestUri, bool validateCertificate = true)
{
using (var connection = _inMemoryTestServer.CreateConnection())
using (var stream = await GetStream(connection.Stream, requestUri, validateCertificate).ConfigureAwait(false))
{
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true))
{
await writer.WriteAsync($"GET {requestUri.PathAndQuery} HTTP/1.0\r\n").ConfigureAwait(false);
await writer.WriteAsync($"Host: {GetHost(requestUri)}\r\n").ConfigureAwait(false);
await writer.WriteAsync("\r\n").ConfigureAwait(false);
}
return await ReadResponse(stream).ConfigureAwait(false);
}
}
internal static string GetHost(Uri requestUri)
{
var authority = requestUri.Authority;
if (requestUri.HostNameType == UriHostNameType.IPv6)
{
// Make sure there's no % scope id. https://github.com/aspnet/KestrelHttpServer/issues/2637
var address = IPAddress.Parse(requestUri.Host);
address = new IPAddress(address.GetAddressBytes()); // Drop scope Id.
if (requestUri.IsDefaultPort)
{
authority = $"[{address}]";
}
else
{
authority = $"[{address}]:{requestUri.Port.ToString(CultureInfo.InvariantCulture)}";
}
}
return authority;
}
public async Task<string> PostAsync(string requestUri, HttpContent content, bool validateCertificate = true)
=> await PostAsync(new Uri(requestUri), content, validateCertificate).ConfigureAwait(false);
public async Task<string> PostAsync(Uri requestUri, HttpContent content, bool validateCertificate = true)
{
using (var connection = _inMemoryTestServer.CreateConnection())
using (var stream = await GetStream(connection.Stream, requestUri, validateCertificate).ConfigureAwait(false))
{
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true))
{
await writer.WriteAsync($"POST {requestUri.PathAndQuery} HTTP/1.0\r\n").ConfigureAwait(false);
await writer.WriteAsync($"Host: {requestUri.Authority}\r\n").ConfigureAwait(false);
await writer.WriteAsync($"Content-Type: {content.Headers.ContentType}\r\n").ConfigureAwait(false);
await writer.WriteAsync($"Content-Length: {content.Headers.ContentLength}\r\n").ConfigureAwait(false);
await writer.WriteAsync("\r\n").ConfigureAwait(false);
}
await content.CopyToAsync(stream).ConfigureAwait(false);
return await ReadResponse(stream).ConfigureAwait(false);
}
}
private static async Task<string> ReadResponse(Stream stream)
{
using (var reader = new StreamReader(stream, Encoding.ASCII, detectEncodingFromByteOrderMarks: true,
bufferSize: 1024, leaveOpen: true))
{
var response = await reader.ReadToEndAsync().DefaultTimeout().ConfigureAwait(false);
var status = GetStatus(response);
new HttpResponseMessage(status).EnsureSuccessStatusCode();
var body = response.Substring(response.IndexOf("\r\n\r\n") + 4);
return body;
}
}
private static HttpStatusCode GetStatus(string response)
{
var statusStart = response.IndexOf(' ') + 1;
var statusEnd = response.IndexOf(' ', statusStart) - 1;
var statusLength = statusEnd - statusStart + 1;
if (statusLength < 1)
{
throw new InvalidDataException($"No StatusCode found in '{response}'");
}
return (HttpStatusCode)int.Parse(response.Substring(statusStart, statusLength));
}
private static async Task<Stream> GetStream(Stream rawStream, Uri requestUri, bool validateCertificate)
{
if (requestUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
{
var sslStream = new SslStream(rawStream, leaveInnerStreamOpen: false, userCertificateValidationCallback:
validateCertificate ? null : (RemoteCertificateValidationCallback)((a, b, c, d) => true));
await sslStream.AuthenticateAsClientAsync(requestUri.Host, clientCertificates: null,
enabledSslProtocols: SslProtocols.Tls11 | SslProtocols.Tls12,
checkCertificateRevocation: validateCertificate).ConfigureAwait(false);
return sslStream;
}
else
{
return rawStream;
}
}
}
}

View File

@ -0,0 +1,60 @@
// 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.Buffers;
using System.IO.Pipelines;
using System.Net;
using System.Threading;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport
{
public class InMemoryTransportConnection : TransportConnection, IDisposable
{
private readonly CancellationTokenSource _connectionClosedTokenSource = new CancellationTokenSource();
private bool _isClosed;
public InMemoryTransportConnection(MemoryPool<byte> memoryPool)
{
MemoryPool = memoryPool;
LocalAddress = IPAddress.Loopback;
RemoteAddress = IPAddress.Loopback;
ConnectionClosed = _connectionClosedTokenSource.Token;
}
public override MemoryPool<byte> MemoryPool { get; }
public override PipeScheduler InputWriterScheduler => PipeScheduler.ThreadPool;
public override PipeScheduler OutputReaderScheduler => PipeScheduler.ThreadPool;
public override void Abort(ConnectionAbortedException abortReason)
{
Input.Complete(abortReason);
}
public void OnClosed()
{
if (_isClosed)
{
return;
}
_isClosed = true;
ThreadPool.QueueUserWorkItem(state =>
{
var self = (InMemoryTransportConnection)state;
self._connectionClosedTokenSource.Cancel();
}, this);
}
public void Dispose()
{
_connectionClosedTokenSource.Dispose();
}
}
}

View File

@ -0,0 +1,44 @@
// 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.Server.Kestrel.Transport.Abstractions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport
{
public class InMemoryTransportFactory : ITransportFactory
{
public ITransport Create(IEndPointInformation endPointInformation, IConnectionDispatcher dispatcher)
{
if (ConnectionDispatcher != null)
{
throw new InvalidOperationException("InMemoryTransportFactory doesn't support creating multiple endpoints");
}
ConnectionDispatcher = dispatcher;
return new NoopTransport();
}
public IConnectionDispatcher ConnectionDispatcher { get; private set; }
private class NoopTransport : ITransport
{
public Task BindAsync()
{
return Task.CompletedTask;
}
public Task StopAsync()
{
return Task.CompletedTask;
}
public Task UnbindAsync()
{
return Task.CompletedTask;
}
}
}
}

View File

@ -0,0 +1,155 @@
// 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.Buffers;
using System.Diagnostics;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport
{
/// <summary>
/// In-memory TestServer
/// </summary
public class TestServer : IDisposable, IStartup
{
private readonly MemoryPool<byte> _memoryPool;
private readonly RequestDelegate _app;
private readonly InMemoryTransportFactory _transportFactory;
private readonly IWebHost _host;
public TestServer(RequestDelegate app)
: this(app, new TestServiceContext())
{
}
public TestServer(RequestDelegate app, TestServiceContext context)
: this(app, context, new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)))
{
// The endpoint is ignored, but this ensures no cert loading happens for HTTPS endpoints.
}
public TestServer(RequestDelegate app, TestServiceContext context, ListenOptions listenOptions)
: this(app, context, options => options.ListenOptions.Add(listenOptions), _ => { })
{
}
public TestServer(RequestDelegate app, TestServiceContext context, Action<ListenOptions> configureListenOptions)
: this(app, context, options =>
{
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
{
KestrelServerOptions = options
};
configureListenOptions(listenOptions);
options.ListenOptions.Add(listenOptions);
},
_ => { })
{
}
public TestServer(RequestDelegate app, TestServiceContext context, Action<KestrelServerOptions> configureKestrel, Action<IServiceCollection> configureServices)
{
_app = app;
Context = context;
_memoryPool = context.MemoryPoolFactory();
_transportFactory = new InMemoryTransportFactory();
HttpClientSlim = new InMemoryHttpClientSlim(this);
var hostBuilder = new WebHostBuilder()
.ConfigureServices(services =>
{
configureServices(services);
services.AddSingleton<IStartup>(this);
services.AddSingleton(context.LoggerFactory);
services.AddSingleton<IServer>(sp =>
{
context.ServerOptions.ApplicationServices = sp;
configureKestrel(context.ServerOptions);
return new KestrelServer(_transportFactory, context);
});
});
_host = hostBuilder.Build();
_host.Start();
}
public int Port => 0;
public TestServiceContext Context { get; }
public InMemoryHttpClientSlim HttpClientSlim { get; }
public InMemoryConnection CreateConnection()
{
var transportConnection = new InMemoryTransportConnection(_memoryPool);
_ = HandleConnection(transportConnection);
return new InMemoryConnection(transportConnection);
}
public Task StopAsync()
{
return _host.StopAsync();
}
public void Dispose()
{
_host.Dispose();
_memoryPool.Dispose();
}
void IStartup.Configure(IApplicationBuilder app)
{
app.Run(_app);
}
IServiceProvider IStartup.ConfigureServices(IServiceCollection services)
{
return services.BuildServiceProvider();
}
private async Task HandleConnection(InMemoryTransportConnection transportConnection)
{
try
{
var middlewareTask = _transportFactory.ConnectionDispatcher.OnConnection(transportConnection);
var transportTask = CancellationTokenAsTask(transportConnection.ConnectionClosed);
await transportTask;
await middlewareTask;
transportConnection.Dispose();
}
catch (Exception ex)
{
Debug.Assert(false, $"Unexpected exception: {ex}.");
}
}
private static Task CancellationTokenAsTask(CancellationToken token)
{
if (token.IsCancellationRequested)
{
return Task.CompletedTask;
}
var tcs = new TaskCompletionSource<object>();
token.Register(() => tcs.SetResult(null));
return tcs.Task;
}
}
}

View File

@ -7,12 +7,13 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Server.Kestrel.Tests;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{
public class UpgradeTests : LoggedTest
{
@ -138,7 +139,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
$"Date: {server.Context.DateHeaderValue}",
"",
"");
await connection.WaitForConnectionClose().DefaultTimeout();
await connection.WaitForConnectionClose();
}
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await upgradeTcs.Task.DefaultTimeout());
@ -275,7 +276,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}, serviceContext))
{
using (var disposables = new DisposableStack<TestConnection>())
using (var disposables = new DisposableStack<InMemoryConnection>())
{
for (var i = 0; i < limit; i++)
{

View File

@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
namespace Microsoft.AspNetCore.Server.Kestrel.Tests
{
public class HttpsConnectionAdapterOptionsTests
{

View File

@ -7,8 +7,8 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="..\shared\**\*.cs" />
<Content Include="..\shared\TestCertificates\*.pfx" CopyToOutputDirectory="PreserveNewest" />
<Compile Include="..\shared\*.cs" LinkBase="shared" />
<Content Include="..\shared\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net461'">

View File

@ -0,0 +1,6 @@
// 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 Microsoft.Extensions.Logging.Testing;
[assembly: ShortClassName]

View File

@ -0,0 +1,60 @@
// 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.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public class ConnectionAdapterTests : LoggedTest
{
[Fact]
public async Task ThrowingSynchronousConnectionAdapterDoesNotCrashServer()
{
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
{
ConnectionAdapters = { new ThrowingConnectionAdapter() }
};
var serviceContext = new TestServiceContext(LoggerFactory);
using (var server = new TestServer(TestApp.EchoApp, serviceContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
// Will throw because the exception in the connection adapter will close the connection.
await Assert.ThrowsAsync<IOException>(async () =>
{
await connection.Send(
"POST / HTTP/1.0",
"Content-Length: 1000",
"\r\n");
for (var i = 0; i < 1000; i++)
{
await connection.Send("a");
await Task.Delay(5);
}
});
}
}
}
private class ThrowingConnectionAdapter : IConnectionAdapter
{
public bool IsHttps => false;
public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
{
throw new Exception();
}
}
}
}

View File

@ -86,13 +86,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
{
var requestStarted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestUnblocked = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var memoryPoolFactory = new DiagnosticMemoryPoolFactory(allowLateReturn: true);
var testContext = new TestServiceContext(LoggerFactory)
{
MemoryPoolFactory = memoryPoolFactory.Create
};
// Abortive shutdown leaves one request hanging
using (var server = new TestServer(TransportSelector.GetWebHostBuilder(new DiagnosticMemoryPoolFactory(allowLateReturn: true).Create), async context =>
using (var server = new TestServer(async context =>
{
requestStarted.SetResult(null);
await requestUnblocked.Task.DefaultTimeout();
await context.Response.WriteAsync("hello world " + context.Request.Protocol);
}, new TestServiceContext(LoggerFactory),
},
testContext,
kestrelOptions =>
{
kestrelOptions.Listen(IPAddress.Loopback, 0, listenOptions =>
@ -114,6 +123,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("is closed. The last processed stream ID was 1."));
Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("Some connections failed to close gracefully during server shutdown."));
Assert.DoesNotContain(TestApplicationErrorLogger.Messages, m => m.Message.Contains("Request finished in"));
requestUnblocked.SetResult(null);
await memoryPoolFactory.WhenAllBlocksReturned(TestConstants.DefaultTimeout);
}
}
}

View File

@ -0,0 +1,854 @@
// 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.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Moq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public class RequestTests : LoggedTest
{
private const int _connectionStartedEventId = 1;
private const int _connectionResetEventId = 19;
private static readonly int _semaphoreWaitTimeout = Debugger.IsAttached ? 10000 : 2500;
public static TheoryData<ListenOptions> ConnectionAdapterData => new TheoryData<ListenOptions>
{
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)),
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
{
ConnectionAdapters = { new PassThroughConnectionAdapter() }
}
};
[Theory]
[InlineData(10 * 1024 * 1024, true)]
// In the following dataset, send at least 2GB.
// Never change to a lower value, otherwise regression testing for
// https://github.com/aspnet/KestrelHttpServer/issues/520#issuecomment-188591242
// will be lost.
[InlineData((long)int.MaxValue + 1, false)]
public void LargeUpload(long contentLength, bool checkBytes)
{
const int bufferLength = 1024 * 1024;
Assert.True(contentLength % bufferLength == 0, $"{nameof(contentLength)} sent must be evenly divisible by {bufferLength}.");
Assert.True(bufferLength % 256 == 0, $"{nameof(bufferLength)} must be evenly divisible by 256");
var builder = TransportSelector.GetWebHostBuilder()
.ConfigureServices(AddTestLogging)
.UseKestrel(options =>
{
options.Limits.MaxRequestBodySize = contentLength;
options.Limits.MinRequestBodyDataRate = null;
})
.UseUrls("http://127.0.0.1:0/")
.Configure(app =>
{
app.Run(async context =>
{
// Read the full request body
long total = 0;
var receivedBytes = new byte[bufferLength];
var received = 0;
while ((received = await context.Request.Body.ReadAsync(receivedBytes, 0, receivedBytes.Length)) > 0)
{
if (checkBytes)
{
for (var i = 0; i < received; i++)
{
// Do not use Assert.Equal here, it is to slow for this hot path
Assert.True((byte)((total + i) % 256) == receivedBytes[i], "Data received is incorrect");
}
}
total += received;
}
await context.Response.WriteAsync(total.ToString(CultureInfo.InvariantCulture));
});
});
using (var host = builder.Build())
{
host.Start();
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.Connect(new IPEndPoint(IPAddress.Loopback, host.GetPort()));
socket.Send(Encoding.ASCII.GetBytes($"POST / HTTP/1.0\r\nContent-Length: {contentLength}\r\n\r\n"));
var contentBytes = new byte[bufferLength];
if (checkBytes)
{
for (var i = 0; i < contentBytes.Length; i++)
{
contentBytes[i] = (byte)i;
}
}
for (var i = 0; i < contentLength / contentBytes.Length; i++)
{
socket.Send(contentBytes);
}
var response = new StringBuilder();
var responseBytes = new byte[4096];
var received = 0;
while ((received = socket.Receive(responseBytes)) > 0)
{
response.Append(Encoding.ASCII.GetString(responseBytes, 0, received));
}
Assert.Contains(contentLength.ToString(CultureInfo.InvariantCulture), response.ToString());
}
}
}
[Fact]
public Task RemoteIPv4Address()
{
return TestRemoteIPAddress("127.0.0.1", "127.0.0.1", "127.0.0.1");
}
[ConditionalFact]
[IPv6SupportedCondition]
public Task RemoteIPv6Address()
{
return TestRemoteIPAddress("[::1]", "[::1]", "::1");
}
[Fact]
public async Task DoesNotHangOnConnectionCloseRequest()
{
var builder = TransportSelector.GetWebHostBuilder()
.UseKestrel()
.UseUrls("http://127.0.0.1:0")
.ConfigureServices(AddTestLogging)
.Configure(app =>
{
app.Run(async context =>
{
await context.Response.WriteAsync("hello, world");
});
});
using (var host = builder.Build())
using (var client = new HttpClient())
{
host.Start();
client.DefaultRequestHeaders.Connection.Clear();
client.DefaultRequestHeaders.Connection.Add("close");
var response = await client.GetAsync($"http://127.0.0.1:{host.GetPort()}/");
response.EnsureSuccessStatusCode();
}
}
[Fact]
public async Task ConnectionResetPriorToRequestIsLoggedAsDebug()
{
var connectionStarted = new SemaphoreSlim(0);
var connectionReset = new SemaphoreSlim(0);
var loggedHigherThanDebug = false;
var mockLogger = new Mock<ILogger>();
mockLogger
.Setup(logger => logger.IsEnabled(It.IsAny<LogLevel>()))
.Returns(true);
mockLogger
.Setup(logger => logger.Log(It.IsAny<LogLevel>(), It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()))
.Callback<LogLevel, EventId, object, Exception, Func<object, Exception, string>>((logLevel, eventId, state, exception, formatter) =>
{
Logger.Log(logLevel, eventId, state, exception, formatter);
if (eventId.Id == _connectionStartedEventId)
{
connectionStarted.Release();
}
else if (eventId.Id == _connectionResetEventId)
{
connectionReset.Release();
}
if (logLevel > LogLevel.Debug)
{
loggedHigherThanDebug = true;
}
});
var mockLoggerFactory = new Mock<ILoggerFactory>();
mockLoggerFactory
.Setup(factory => factory.CreateLogger(It.IsAny<string>()))
.Returns(Logger);
mockLoggerFactory
.Setup(factory => factory.CreateLogger(It.IsIn("Microsoft.AspNetCore.Server.Kestrel",
"Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv",
"Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets")))
.Returns(mockLogger.Object);
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(mockLoggerFactory.Object)))
{
using (var connection = server.CreateConnection())
{
// Wait until connection is established
Assert.True(await connectionStarted.WaitAsync(TestConstants.DefaultTimeout));
connection.Reset();
}
// If the reset is correctly logged as Debug, the wait below should complete shortly.
// This check MUST come before disposing the server, otherwise there's a race where the RST
// is still in flight when the connection is aborted, leading to the reset never being received
// and therefore not logged.
Assert.True(await connectionReset.WaitAsync(TestConstants.DefaultTimeout));
}
Assert.False(loggedHigherThanDebug);
}
[Fact]
public async Task ConnectionResetBetweenRequestsIsLoggedAsDebug()
{
var connectionReset = new SemaphoreSlim(0);
var loggedHigherThanDebug = false;
var mockLogger = new Mock<ILogger>();
mockLogger
.Setup(logger => logger.IsEnabled(It.IsAny<LogLevel>()))
.Returns(true);
mockLogger
.Setup(logger => logger.Log(It.IsAny<LogLevel>(), It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()))
.Callback<LogLevel, EventId, object, Exception, Func<object, Exception, string>>((logLevel, eventId, state, exception, formatter) =>
{
Logger.Log(logLevel, eventId, state, exception, formatter);
if (eventId.Id == _connectionResetEventId)
{
connectionReset.Release();
}
if (logLevel > LogLevel.Debug)
{
loggedHigherThanDebug = true;
}
});
var mockLoggerFactory = new Mock<ILoggerFactory>();
mockLoggerFactory
.Setup(factory => factory.CreateLogger(It.IsAny<string>()))
.Returns(Logger);
mockLoggerFactory
.Setup(factory => factory.CreateLogger(It.IsIn("Microsoft.AspNetCore.Server.Kestrel",
"Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv",
"Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets")))
.Returns(mockLogger.Object);
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(mockLoggerFactory.Object)))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
// Make sure the response is fully received, so a write failure (e.g. EPIPE) doesn't cause
// a more critical log message.
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 0",
"",
"");
connection.Reset();
// Force a reset
}
// If the reset is correctly logged as Debug, the wait below should complete shortly.
// This check MUST come before disposing the server, otherwise there's a race where the RST
// is still in flight when the connection is aborted, leading to the reset never being received
// and therefore not logged.
Assert.True(await connectionReset.WaitAsync(TestConstants.DefaultTimeout));
}
Assert.False(loggedHigherThanDebug);
}
[Fact]
public async Task ConnectionResetMidRequestIsLoggedAsDebug()
{
var requestStarted = new SemaphoreSlim(0);
var connectionReset = new SemaphoreSlim(0);
var connectionClosing = new SemaphoreSlim(0);
var loggedHigherThanDebug = false;
var mockLogger = new Mock<ILogger>();
mockLogger
.Setup(logger => logger.IsEnabled(It.IsAny<LogLevel>()))
.Returns(true);
mockLogger
.Setup(logger => logger.Log(It.IsAny<LogLevel>(), It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()))
.Callback<LogLevel, EventId, object, Exception, Func<object, Exception, string>>((logLevel, eventId, state, exception, formatter) =>
{
Logger.Log(logLevel, eventId, state, exception, formatter);
var log = $"Log {logLevel}[{eventId}]: {formatter(state, exception)} {exception}";
TestOutputHelper.WriteLine(log);
if (eventId.Id == _connectionResetEventId)
{
connectionReset.Release();
}
if (logLevel > LogLevel.Debug)
{
loggedHigherThanDebug = true;
}
});
var mockLoggerFactory = new Mock<ILoggerFactory>();
mockLoggerFactory
.Setup(factory => factory.CreateLogger(It.IsAny<string>()))
.Returns(Logger);
mockLoggerFactory
.Setup(factory => factory.CreateLogger(It.IsIn("Microsoft.AspNetCore.Server.Kestrel",
"Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv",
"Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets")))
.Returns(mockLogger.Object);
using (var server = new TestServer(async context =>
{
requestStarted.Release();
await connectionClosing.WaitAsync();
},
new TestServiceContext(mockLoggerFactory.Object)))
{
using (var connection = server.CreateConnection())
{
await connection.SendEmptyGet();
// Wait until connection is established
Assert.True(await requestStarted.WaitAsync(TestConstants.DefaultTimeout), "request should have started");
connection.Reset();
}
// If the reset is correctly logged as Debug, the wait below should complete shortly.
// This check MUST come before disposing the server, otherwise there's a race where the RST
// is still in flight when the connection is aborted, leading to the reset never being received
// and therefore not logged.
Assert.True(await connectionReset.WaitAsync(TestConstants.DefaultTimeout), "Connection reset event should have been logged");
connectionClosing.Release();
}
Assert.False(loggedHigherThanDebug, "Logged event should not have been higher than debug.");
}
[Fact]
public async Task ThrowsOnReadAfterConnectionError()
{
var requestStarted = new SemaphoreSlim(0);
var connectionReset = new SemaphoreSlim(0);
var appDone = new SemaphoreSlim(0);
var expectedExceptionThrown = false;
var builder = TransportSelector.GetWebHostBuilder()
.ConfigureServices(AddTestLogging)
.UseKestrel()
.UseUrls("http://127.0.0.1:0")
.Configure(app => app.Run(async context =>
{
requestStarted.Release();
Assert.True(await connectionReset.WaitAsync(_semaphoreWaitTimeout));
try
{
await context.Request.Body.ReadAsync(new byte[1], 0, 1);
}
catch (ConnectionResetException)
{
expectedExceptionThrown = true;
}
appDone.Release();
}));
using (var host = builder.Build())
{
host.Start();
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.Connect(new IPEndPoint(IPAddress.Loopback, host.GetPort()));
socket.LingerState = new LingerOption(true, 0);
socket.Send(Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\nContent-Length: 1\r\n\r\n"));
Assert.True(await requestStarted.WaitAsync(_semaphoreWaitTimeout));
}
connectionReset.Release();
Assert.True(await appDone.WaitAsync(_semaphoreWaitTimeout));
Assert.True(expectedExceptionThrown);
}
}
[Fact]
public async Task RequestAbortedTokenFiredOnClientFIN()
{
var appStarted = new SemaphoreSlim(0);
var requestAborted = new SemaphoreSlim(0);
var builder = TransportSelector.GetWebHostBuilder()
.UseKestrel()
.UseUrls("http://127.0.0.1:0")
.ConfigureServices(AddTestLogging)
.Configure(app => app.Run(async context =>
{
appStarted.Release();
var token = context.RequestAborted;
token.Register(() => requestAborted.Release(2));
await requestAborted.WaitAsync().DefaultTimeout();
}));
using (var host = builder.Build())
{
host.Start();
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.Connect(new IPEndPoint(IPAddress.Loopback, host.GetPort()));
socket.Send(Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n"));
await appStarted.WaitAsync();
socket.Shutdown(SocketShutdown.Send);
await requestAborted.WaitAsync().DefaultTimeout();
}
}
}
[Fact]
public void AbortingTheConnectionSendsFIN()
{
var builder = TransportSelector.GetWebHostBuilder()
.UseKestrel()
.UseUrls("http://127.0.0.1:0")
.ConfigureServices(AddTestLogging)
.Configure(app => app.Run(context =>
{
context.Abort();
return Task.CompletedTask;
}));
using (var host = builder.Build())
{
host.Start();
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.Connect(new IPEndPoint(IPAddress.Loopback, host.GetPort()));
socket.Send(Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n"));
int result = socket.Receive(new byte[32]);
Assert.Equal(0, result);
}
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ConnectionClosedTokenFiresOnClientFIN(ListenOptions listenOptions)
{
var testContext = new TestServiceContext(LoggerFactory);
var appStartedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var connectionClosedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
using (var server = new TestServer(context =>
{
appStartedTcs.SetResult(null);
var connectionLifetimeFeature = context.Features.Get<IConnectionLifetimeFeature>();
connectionLifetimeFeature.ConnectionClosed.Register(() => connectionClosedTcs.SetResult(null));
return Task.CompletedTask;
}, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
await appStartedTcs.Task.DefaultTimeout();
connection.Shutdown(SocketShutdown.Send);
await connectionClosedTcs.Task.DefaultTimeout();
}
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ConnectionClosedTokenFiresOnServerFIN(ListenOptions listenOptions)
{
var testContext = new TestServiceContext(LoggerFactory);
var connectionClosedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
using (var server = new TestServer(context =>
{
var connectionLifetimeFeature = context.Features.Get<IConnectionLifetimeFeature>();
connectionLifetimeFeature.ConnectionClosed.Register(() => connectionClosedTcs.SetResult(null));
return Task.CompletedTask;
}, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"Connection: close",
"",
"");
await connectionClosedTcs.Task.DefaultTimeout();
await connection.ReceiveEnd($"HTTP/1.1 200 OK",
"Connection: close",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 0",
"",
"");
}
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ConnectionClosedTokenFiresOnServerAbort(ListenOptions listenOptions)
{
var testContext = new TestServiceContext(LoggerFactory);
var connectionClosedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
using (var server = new TestServer(context =>
{
var connectionLifetimeFeature = context.Features.Get<IConnectionLifetimeFeature>();
connectionLifetimeFeature.ConnectionClosed.Register(() => connectionClosedTcs.SetResult(null));
context.Abort();
return Task.CompletedTask;
}, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
await connectionClosedTcs.Task.DefaultTimeout();
await connection.ReceiveForcedEnd();
}
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task RequestsCanBeAbortedMidRead(ListenOptions listenOptions)
{
const int applicationAbortedConnectionId = 34;
var testContext = new TestServiceContext(LoggerFactory);
var readTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var registrationTcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestId = 0;
using (var server = new TestServer(async httpContext =>
{
requestId++;
var response = httpContext.Response;
var request = httpContext.Request;
var lifetime = httpContext.Features.Get<IHttpRequestLifetimeFeature>();
lifetime.RequestAborted.Register(() => registrationTcs.TrySetResult(requestId));
if (requestId == 1)
{
response.Headers["Content-Length"] = new[] { "5" };
await response.WriteAsync("World");
}
else
{
var readTask = request.Body.CopyToAsync(Stream.Null);
lifetime.Abort();
try
{
await readTask;
}
catch (Exception ex)
{
readTcs.SetException(ex);
throw;
}
readTcs.SetException(new Exception("This shouldn't be reached."));
}
}, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
// Full request and response
await connection.Send(
"POST / HTTP/1.1",
"Host:",
"Content-Length: 5",
"",
"Hello");
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {testContext.DateHeaderValue}",
"Content-Length: 5",
"",
"World");
// Never send the body so CopyToAsync always fails.
await connection.Send("POST / HTTP/1.1",
"Host:",
"Content-Length: 5",
"",
"");
await connection.WaitForConnectionClose();
}
}
await Assert.ThrowsAsync<TaskCanceledException>(async () => await readTcs.Task);
// The cancellation token for only the last request should be triggered.
var abortedRequestId = await registrationTcs.Task;
Assert.Equal(2, abortedRequestId);
Assert.Single(TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel" &&
w.EventId == applicationAbortedConnectionId));
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ServerCanAbortConnectionAfterUnobservedClose(ListenOptions listenOptions)
{
const int connectionPausedEventId = 4;
const int connectionFinSentEventId = 7;
const int maxRequestBufferSize = 4096;
var readCallbackUnwired = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var clientClosedConnection = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var serverClosedConnection = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var appFuncCompleted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var mockLogger = new Mock<ILogger>();
mockLogger
.Setup(logger => logger.IsEnabled(It.IsAny<LogLevel>()))
.Returns(true);
mockLogger
.Setup(logger => logger.Log(It.IsAny<LogLevel>(), It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()))
.Callback<LogLevel, EventId, object, Exception, Func<object, Exception, string>>((logLevel, eventId, state, exception, formatter) =>
{
if (eventId.Id == connectionPausedEventId)
{
readCallbackUnwired.TrySetResult(null);
}
else if (eventId.Id == connectionFinSentEventId)
{
serverClosedConnection.SetResult(null);
}
Logger.Log(logLevel, eventId, state, exception, formatter);
});
var mockLoggerFactory = new Mock<ILoggerFactory>();
mockLoggerFactory
.Setup(factory => factory.CreateLogger(It.IsAny<string>()))
.Returns(Logger);
mockLoggerFactory
.Setup(factory => factory.CreateLogger(It.IsIn("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv",
"Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets")))
.Returns(mockLogger.Object);
var mockKestrelTrace = new Mock<KestrelTrace>(Logger) { CallBase = true };
var testContext = new TestServiceContext(mockLoggerFactory.Object)
{
Log = mockKestrelTrace.Object,
ServerOptions =
{
Limits =
{
MaxRequestBufferSize = maxRequestBufferSize,
MaxRequestLineSize = maxRequestBufferSize,
MaxRequestHeadersTotalSize = maxRequestBufferSize,
}
}
};
var scratchBuffer = new byte[maxRequestBufferSize * 8];
using (var server = new TestServer(async context =>
{
await clientClosedConnection.Task;
context.Abort();
await serverClosedConnection.Task;
appFuncCompleted.SetResult(null);
}, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"POST / HTTP/1.1",
"Host:",
$"Content-Length: {scratchBuffer.Length}",
"",
"");
var ignore = connection.Stream.WriteAsync(scratchBuffer, 0, scratchBuffer.Length);
// Wait until the read callback is no longer hooked up so that the connection disconnect isn't observed.
await readCallbackUnwired.Task.DefaultTimeout();
}
clientClosedConnection.SetResult(null);
await appFuncCompleted.Task.DefaultTimeout();
}
mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny<string>()), Times.Once());
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task AppCanHandleClientAbortingConnectionMidRequest(ListenOptions listenOptions)
{
var readTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var appStartedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var mockKestrelTrace = new Mock<KestrelTrace>(Logger) { CallBase = true };
var testContext = new TestServiceContext()
{
Log = mockKestrelTrace.Object,
};
var scratchBuffer = new byte[4096];
using (var server = new TestServer(async context =>
{
appStartedTcs.SetResult(null);
try
{
await context.Request.Body.CopyToAsync(Stream.Null);;
}
catch (Exception ex)
{
readTcs.SetException(ex);
throw;
}
readTcs.SetException(new Exception("This shouldn't be reached."));
}, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"POST / HTTP/1.1",
"Host:",
$"Content-Length: {scratchBuffer.Length * 2}",
"",
"");
await appStartedTcs.Task.DefaultTimeout();
await connection.Stream.WriteAsync(scratchBuffer, 0, scratchBuffer.Length);
connection.Reset();
}
await Assert.ThrowsAnyAsync<IOException>(() => readTcs.Task).DefaultTimeout();
}
mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny<string>()), Times.Once());
}
private async Task TestRemoteIPAddress(string registerAddress, string requestAddress, string expectAddress)
{
var builder = TransportSelector.GetWebHostBuilder()
.UseKestrel()
.UseUrls($"http://{registerAddress}:0")
.ConfigureServices(AddTestLogging)
.Configure(app =>
{
app.Run(async context =>
{
var connection = context.Connection;
await context.Response.WriteAsync(JsonConvert.SerializeObject(new
{
RemoteIPAddress = connection.RemoteIpAddress?.ToString(),
RemotePort = connection.RemotePort,
LocalIPAddress = connection.LocalIpAddress?.ToString(),
LocalPort = connection.LocalPort
}));
});
});
using (var host = builder.Build())
using (var client = new HttpClient())
{
host.Start();
var response = await client.GetAsync($"http://{requestAddress}:{host.GetPort()}/");
response.EnsureSuccessStatusCode();
var connectionFacts = await response.Content.ReadAsStringAsync();
Assert.NotEmpty(connectionFacts);
var facts = JsonConvert.DeserializeObject<JObject>(connectionFacts);
Assert.Equal(expectAddress, facts["RemoteIPAddress"].Value<string>());
Assert.NotEmpty(facts["RemotePort"].Value<string>());
}
}
}
}

View File

@ -0,0 +1,945 @@
// 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.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public class ResponseTests : TestApplicationErrorLoggerLoggedTest
{
public static TheoryData<ListenOptions> ConnectionAdapterData => new TheoryData<ListenOptions>
{
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)),
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
{
ConnectionAdapters = { new PassThroughConnectionAdapter() }
}
};
[Fact]
public async Task LargeDownload()
{
var hostBuilder = TransportSelector.GetWebHostBuilder()
.UseKestrel()
.UseUrls("http://127.0.0.1:0/")
.ConfigureServices(AddTestLogging)
.Configure(app =>
{
app.Run(async context =>
{
var bytes = new byte[1024];
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = (byte)i;
}
context.Response.ContentLength = bytes.Length * 1024;
for (int i = 0; i < 1024; i++)
{
await context.Response.Body.WriteAsync(bytes, 0, bytes.Length);
}
});
});
using (var host = hostBuilder.Build())
{
host.Start();
using (var client = new HttpClient())
{
var response = await client.GetAsync($"http://127.0.0.1:{host.GetPort()}/");
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStreamAsync();
// Read the full response body
var total = 0;
var bytes = new byte[1024];
var count = await responseBody.ReadAsync(bytes, 0, bytes.Length);
while (count > 0)
{
for (int i = 0; i < count; i++)
{
Assert.Equal(total % 256, bytes[i]);
total++;
}
count = await responseBody.ReadAsync(bytes, 0, bytes.Length);
}
}
}
}
[Theory, MemberData(nameof(NullHeaderData))]
public async Task IgnoreNullHeaderValues(string headerName, StringValues headerValue, string expectedValue)
{
var hostBuilder = TransportSelector.GetWebHostBuilder()
.UseKestrel()
.UseUrls("http://127.0.0.1:0/")
.ConfigureServices(AddTestLogging)
.Configure(app =>
{
app.Run(async context =>
{
context.Response.Headers.Add(headerName, headerValue);
await context.Response.WriteAsync("");
});
});
using (var host = hostBuilder.Build())
{
host.Start();
using (var client = new HttpClient())
{
var response = await client.GetAsync($"http://127.0.0.1:{host.GetPort()}/");
response.EnsureSuccessStatusCode();
var headers = response.Headers;
if (expectedValue == null)
{
Assert.False(headers.Contains(headerName));
}
else
{
Assert.True(headers.Contains(headerName));
Assert.Equal(headers.GetValues(headerName).Single(), expectedValue);
}
}
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task WriteAfterConnectionCloseNoops(ListenOptions listenOptions)
{
var connectionClosed = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestStarted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var appCompleted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
using (var server = new TestServer(async httpContext =>
{
try
{
requestStarted.SetResult(null);
await connectionClosed.Task.DefaultTimeout();
httpContext.Response.ContentLength = 12;
await httpContext.Response.WriteAsync("hello, world");
appCompleted.TrySetResult(null);
}
catch (Exception ex)
{
appCompleted.TrySetException(ex);
}
}, new TestServiceContext(LoggerFactory), listenOptions))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
await requestStarted.Task.DefaultTimeout();
connection.ShutdownSend();
await connection.WaitForConnectionClose();
}
connectionClosed.SetResult(null);
await appCompleted.Task.DefaultTimeout();
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ThrowsOnWriteWithRequestAbortedTokenAfterRequestIsAborted(ListenOptions listenOptions)
{
// This should match _maxBytesPreCompleted in SocketOutput
var maxBytesPreCompleted = 65536;
// Ensure string is long enough to disable write-behind buffering
var largeString = new string('a', maxBytesPreCompleted + 1);
var writeTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestAbortedWh = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestStartWh = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
using (var server = new TestServer(async httpContext =>
{
requestStartWh.SetResult(null);
var response = httpContext.Response;
var request = httpContext.Request;
var lifetime = httpContext.Features.Get<IHttpRequestLifetimeFeature>();
lifetime.RequestAborted.Register(() => requestAbortedWh.SetResult(null));
await requestAbortedWh.Task.DefaultTimeout();
try
{
await response.WriteAsync(largeString, lifetime.RequestAborted);
}
catch (Exception ex)
{
writeTcs.SetException(ex);
throw;
}
writeTcs.SetException(new Exception("This shouldn't be reached."));
}, new TestServiceContext(LoggerFactory), listenOptions))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"POST / HTTP/1.1",
"Host:",
"Content-Length: 0",
"",
"");
await requestStartWh.Task.DefaultTimeout();
}
// Write failed - can throw TaskCanceledException or OperationCanceledException,
// depending on how far the canceled write goes.
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await writeTcs.Task).DefaultTimeout();
// RequestAborted tripped
await requestAbortedWh.Task.DefaultTimeout();
}
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task WritingToConnectionAfterUnobservedCloseTriggersRequestAbortedToken(ListenOptions listenOptions)
{
const int connectionPausedEventId = 4;
const int maxRequestBufferSize = 4096;
var requestAborted = false;
var readCallbackUnwired = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var clientClosedConnection = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var writeTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var mockKestrelTrace = new Mock<KestrelTrace>(Logger) { CallBase = true };
var mockLogger = new Mock<ILogger>();
mockLogger
.Setup(logger => logger.IsEnabled(It.IsAny<LogLevel>()))
.Returns(true);
mockLogger
.Setup(logger => logger.Log(It.IsAny<LogLevel>(), It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()))
.Callback<LogLevel, EventId, object, Exception, Func<object, Exception, string>>((logLevel, eventId, state, exception, formatter) =>
{
if (eventId.Id == connectionPausedEventId)
{
readCallbackUnwired.TrySetResult(null);
}
Logger.Log(logLevel, eventId, state, exception, formatter);
});
var mockLoggerFactory = new Mock<ILoggerFactory>();
mockLoggerFactory
.Setup(factory => factory.CreateLogger(It.IsAny<string>()))
.Returns(Logger);
mockLoggerFactory
.Setup(factory => factory.CreateLogger(It.IsIn("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv",
"Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets")))
.Returns(mockLogger.Object);
var testContext = new TestServiceContext(mockLoggerFactory.Object)
{
Log = mockKestrelTrace.Object,
ServerOptions =
{
Limits =
{
MaxRequestBufferSize = maxRequestBufferSize,
MaxRequestLineSize = maxRequestBufferSize,
MaxRequestHeadersTotalSize = maxRequestBufferSize,
}
}
};
var scratchBuffer = new byte[maxRequestBufferSize * 8];
using (var server = new TestServer(async context =>
{
context.RequestAborted.Register(() =>
{
requestAborted = true;
});
await clientClosedConnection.Task;
try
{
for (var i = 0; i < 1000; i++)
{
await context.Response.Body.WriteAsync(scratchBuffer, 0, scratchBuffer.Length, context.RequestAborted);
await Task.Delay(10);
}
}
catch (Exception ex)
{
writeTcs.SetException(ex);
throw;
}
writeTcs.SetException(new Exception("This shouldn't be reached."));
}, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"POST / HTTP/1.1",
"Host:",
$"Content-Length: {scratchBuffer.Length}",
"",
"");
var ignore = connection.Stream.WriteAsync(scratchBuffer, 0, scratchBuffer.Length);
// Wait until the read callback is no longer hooked up so that the connection disconnect isn't observed.
await readCallbackUnwired.Task.DefaultTimeout();
}
clientClosedConnection.SetResult(null);
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => writeTcs.Task).DefaultTimeout();
}
mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny<string>()), Times.Once());
Assert.True(requestAborted);
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task AppCanHandleClientAbortingConnectionMidResponse(ListenOptions listenOptions)
{
const int connectionResetEventId = 19;
const int connectionFinEventId = 6;
//const int connectionStopEventId = 2;
const int responseBodySegmentSize = 65536;
const int responseBodySegmentCount = 100;
var appCompletedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestAborted = false;
var scratchBuffer = new byte[responseBodySegmentSize];
using (var server = new TestServer(async context =>
{
context.RequestAborted.Register(() =>
{
requestAborted = true;
});
for (var i = 0; i < responseBodySegmentCount; i++)
{
await context.Response.Body.WriteAsync(scratchBuffer, 0, scratchBuffer.Length);
await Task.Delay(10);
}
appCompletedTcs.SetResult(null);
}, new TestServiceContext(LoggerFactory), listenOptions))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
// Read just part of the response and close the connection.
// https://github.com/aspnet/KestrelHttpServer/issues/2554
await connection.Stream.ReadAsync(scratchBuffer, 0, scratchBuffer.Length);
connection.Reset();
}
await appCompletedTcs.Task.DefaultTimeout();
// After the app is done with the write loop, the connection reset should be logged.
// On Linux and macOS, the connection close is still sometimes observed as a FIN despite the LingerState.
var presShutdownTransportLogs = TestSink.Writes.Where(
w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" ||
w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets");
var connectionResetLogs = presShutdownTransportLogs.Where(
w => w.EventId == connectionResetEventId ||
(!TestPlatformHelper.IsWindows && w.EventId == connectionFinEventId));
Assert.NotEmpty(connectionResetLogs);
}
// TODO: Figure out what the following assertion is flaky. The server shouldn't shutdown before all
// the connections are closed, yet sometimes the connection stop log isn't observed here.
//var coreLogs = TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel");
//Assert.Single(coreLogs.Where(w => w.EventId == connectionStopEventId));
Assert.True(requestAborted, "RequestAborted token didn't fire.");
var transportLogs = TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel" ||
w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" ||
w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets");
Assert.Empty(transportLogs.Where(w => w.LogLevel > LogLevel.Debug));
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task ClientAbortingConnectionImmediatelyIsNotLoggedHigherThanDebug(ListenOptions listenOptions)
{
// Attempt multiple connections to be extra sure the resets are consistently logged appropriately.
const int numConnections = 10;
// There's not guarantee that the app even gets invoked in this test. The connection reset can be observed
// as early as accept.
using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions))
{
for (var i = 0; i < numConnections; i++)
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
connection.Reset();
}
}
}
var transportLogs = TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" ||
w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets");
// The "Microsoft.AspNetCore.Server.Kestrel" logger may contain info level logs because resetting the connection can cause
// partial headers to be read leading to a bad request.
var coreLogs = TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel");
Assert.Empty(transportLogs.Where(w => w.LogLevel > LogLevel.Debug));
Assert.Empty(coreLogs.Where(w => w.LogLevel > LogLevel.Information));
}
[Fact]
public async Task ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate()
{
using (StartLog(out var loggerFactory, "ConnClosedWhenRespDoesNotSatisfyMin"))
{
var logger = loggerFactory.CreateLogger($"{ typeof(ResponseTests).FullName}.{ nameof(ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate)}");
const int chunkSize = 1024;
const int chunks = 256 * 1024;
var responseSize = chunks * chunkSize;
var chunkData = new byte[chunkSize];
var responseRateTimeoutMessageLogged = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var connectionStopMessageLogged = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestAborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var appFuncCompleted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var mockKestrelTrace = new Mock<KestrelTrace>(Logger) { CallBase = true };
mockKestrelTrace
.Setup(trace => trace.ResponseMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>()))
.Callback(() => responseRateTimeoutMessageLogged.SetResult(null));
mockKestrelTrace
.Setup(trace => trace.ConnectionStop(It.IsAny<string>()))
.Callback(() => connectionStopMessageLogged.SetResult(null));
var testContext = new TestServiceContext
{
LoggerFactory = loggerFactory,
Log = mockKestrelTrace.Object,
ServerOptions =
{
Limits =
{
MinResponseDataRate = new MinDataRate(bytesPerSecond: 1024 * 1024, gracePeriod: TimeSpan.FromSeconds(2))
}
}
};
testContext.InitializeHeartbeat();
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
listenOptions.ConnectionAdapters.Add(new LoggingConnectionAdapter(loggerFactory.CreateLogger<LoggingConnectionAdapter>()));
var appLogger = loggerFactory.CreateLogger("App");
async Task App(HttpContext context)
{
appLogger.LogInformation("Request received");
context.RequestAborted.Register(() => requestAborted.SetResult(null));
context.Response.ContentLength = responseSize;
try
{
for (var i = 0; i < chunks; i++)
{
await context.Response.Body.WriteAsync(chunkData, 0, chunkData.Length, context.RequestAborted);
appLogger.LogInformation("Wrote chunk of {chunkSize} bytes", chunkSize);
}
}
catch (OperationCanceledException)
{
appFuncCompleted.SetResult(null);
throw;
}
}
using (var server = new TestServer(App, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
logger.LogInformation("Sending request");
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
logger.LogInformation("Sent request");
var sw = Stopwatch.StartNew();
logger.LogInformation("Waiting for connection to abort.");
await requestAborted.Task.DefaultTimeout();
await responseRateTimeoutMessageLogged.Task.DefaultTimeout();
await connectionStopMessageLogged.Task.DefaultTimeout();
await appFuncCompleted.Task.DefaultTimeout();
await AssertStreamAborted(connection.Reader.BaseStream, chunkSize * chunks);
sw.Stop();
logger.LogInformation("Connection was aborted after {totalMilliseconds}ms.", sw.ElapsedMilliseconds);
}
}
}
}
[Fact]
public async Task HttpsConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate()
{
const int chunkSize = 1024;
const int chunks = 256 * 1024;
var chunkData = new byte[chunkSize];
var certificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
var responseRateTimeoutMessageLogged = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var connectionStopMessageLogged = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var aborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var appFuncCompleted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var mockKestrelTrace = new Mock<KestrelTrace>(Logger) { CallBase = true };
mockKestrelTrace
.Setup(trace => trace.ResponseMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>()))
.Callback(() => responseRateTimeoutMessageLogged.SetResult(null));
mockKestrelTrace
.Setup(trace => trace.ConnectionStop(It.IsAny<string>()))
.Callback(() => connectionStopMessageLogged.SetResult(null));
var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object)
{
ServerOptions =
{
Limits =
{
MinResponseDataRate = new MinDataRate(bytesPerSecond: 1024 * 1024, gracePeriod: TimeSpan.FromSeconds(2))
}
}
};
testContext.InitializeHeartbeat();
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
{
ConnectionAdapters =
{
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = certificate })
}
};
using (var server = new TestServer(async context =>
{
context.RequestAborted.Register(() =>
{
aborted.SetResult(null);
});
context.Response.ContentLength = chunks * chunkSize;
try
{
for (var i = 0; i < chunks; i++)
{
await context.Response.Body.WriteAsync(chunkData, 0, chunkData.Length, context.RequestAborted);
}
}
catch (OperationCanceledException)
{
appFuncCompleted.SetResult(null);
throw;
}
}, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
using (var sslStream = new SslStream(connection.Reader.BaseStream, false, (sender, cert, chain, errors) => true, null))
{
await sslStream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
var request = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n");
await sslStream.WriteAsync(request, 0, request.Length);
await aborted.Task.DefaultTimeout();
await responseRateTimeoutMessageLogged.Task.DefaultTimeout();
await connectionStopMessageLogged.Task.DefaultTimeout();
await appFuncCompleted.Task.DefaultTimeout();
await AssertStreamAborted(connection.Reader.BaseStream, chunkSize * chunks);
}
}
}
}
[Fact]
public async Task ConnectionClosedWhenBothRequestAndResponseExperienceBackPressure()
{
const int bufferSize = 65536;
const int bufferCount = 100;
var responseSize = bufferCount * bufferSize;
var buffer = new byte[bufferSize];
var responseRateTimeoutMessageLogged = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var connectionStopMessageLogged = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestAborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var copyToAsyncCts = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var mockKestrelTrace = new Mock<KestrelTrace>(Logger) { CallBase = true };
mockKestrelTrace
.Setup(trace => trace.ResponseMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>()))
.Callback(() => responseRateTimeoutMessageLogged.SetResult(null));
mockKestrelTrace
.Setup(trace => trace.ConnectionStop(It.IsAny<string>()))
.Callback(() => connectionStopMessageLogged.SetResult(null));
var testContext = new TestServiceContext
{
LoggerFactory = LoggerFactory,
Log = mockKestrelTrace.Object,
ServerOptions =
{
Limits =
{
MinResponseDataRate = new MinDataRate(bytesPerSecond: 1024 * 1024, gracePeriod: TimeSpan.FromSeconds(2)),
MaxRequestBodySize = responseSize
}
}
};
testContext.InitializeHeartbeat();
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
async Task App(HttpContext context)
{
context.RequestAborted.Register(() =>
{
requestAborted.SetResult(null);
});
try
{
await context.Request.Body.CopyToAsync(context.Response.Body);
}
catch (Exception ex)
{
copyToAsyncCts.SetException(ex);
throw;
}
copyToAsyncCts.SetException(new Exception("This shouldn't be reached."));
}
using (var server = new TestServer(App, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
// Close the connection with the last request so AssertStreamCompleted actually completes.
await connection.Send(
"POST / HTTP/1.1",
"Host:",
$"Content-Length: {responseSize}",
"",
"");
var sendTask = Task.Run(async () =>
{
for (var i = 0; i < bufferCount; i++)
{
await connection.Stream.WriteAsync(buffer, 0, buffer.Length);
await Task.Delay(10);
}
});
await requestAborted.Task.DefaultTimeout();
await responseRateTimeoutMessageLogged.Task.DefaultTimeout();
await connectionStopMessageLogged.Task.DefaultTimeout();
// Expect OperationCanceledException instead of IOException because the server initiated the abort due to a response rate timeout.
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => copyToAsyncCts.Task).DefaultTimeout();
await AssertStreamAborted(connection.Stream, responseSize);
}
}
}
[Fact]
public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseChunks()
{
var chunkSize = 64 * 128 * 1024;
var chunkCount = 4;
var chunkData = new byte[chunkSize];
var requestAborted = false;
var appFuncCompleted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var mockKestrelTrace = new Mock<KestrelTrace>(Logger) { CallBase = true };
var testContext = new TestServiceContext
{
Log = mockKestrelTrace.Object,
ServerOptions =
{
Limits =
{
MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2))
}
}
};
testContext.InitializeHeartbeat();
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
async Task App(HttpContext context)
{
context.RequestAborted.Register(() =>
{
requestAborted = true;
});
for (var i = 0; i < chunkCount; i++)
{
await context.Response.Body.WriteAsync(chunkData, 0, chunkData.Length, context.RequestAborted);
}
appFuncCompleted.SetResult(null);
}
using (var server = new TestServer(App, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
// Close the connection with the last request so AssertStreamCompleted actually completes.
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"Connection: close",
"",
"");
var minTotalOutputSize = chunkCount * chunkSize;
// Make sure consuming a single chunk exceeds the 2 second timeout.
var targetBytesPerSecond = chunkSize / 4;
await AssertStreamCompleted(connection.Reader.BaseStream, minTotalOutputSize, targetBytesPerSecond);
await appFuncCompleted.Task.DefaultTimeout();
mockKestrelTrace.Verify(t => t.ResponseMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny<string>()), Times.Once());
Assert.False(requestAborted);
}
}
}
[Fact]
public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseHeaders()
{
var headerSize = 1024 * 1024; // 1 MB for each header value
var headerCount = 64; // 64 MB of headers per response
var requestCount = 4; // Minimum of 256 MB of total response headers
var headerValue = new string('a', headerSize);
var headerStringValues = new StringValues(Enumerable.Repeat(headerValue, headerCount).ToArray());
var requestAborted = false;
var mockKestrelTrace = new Mock<KestrelTrace>(Logger) { CallBase = true };
var testContext = new TestServiceContext
{
Log = mockKestrelTrace.Object,
ServerOptions =
{
Limits =
{
MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2))
}
}
};
testContext.InitializeHeartbeat();
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
async Task App(HttpContext context)
{
context.RequestAborted.Register(() =>
{
requestAborted = true;
});
context.Response.Headers[$"X-Custom-Header"] = headerStringValues;
context.Response.ContentLength = 0;
await context.Response.Body.FlushAsync();
}
using (var server = new TestServer(App, testContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
for (var i = 0; i < requestCount - 1; i++)
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
}
// Close the connection with the last request so AssertStreamCompleted actually completes.
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"Connection: close",
"",
"");
var responseSize = headerSize * headerCount;
var minTotalOutputSize = requestCount * responseSize;
// Make sure consuming a single set of response headers exceeds the 2 second timeout.
var targetBytesPerSecond = responseSize / 4;
await AssertStreamCompleted(connection.Reader.BaseStream, minTotalOutputSize, targetBytesPerSecond);
mockKestrelTrace.Verify(t => t.ResponseMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny<string>()), Times.Once());
Assert.False(requestAborted);
}
}
}
private async Task AssertStreamAborted(Stream stream, int totalBytes)
{
var receiveBuffer = new byte[64 * 1024];
var totalReceived = 0;
try
{
while (totalReceived < totalBytes)
{
var bytes = await stream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length).DefaultTimeout();
if (bytes == 0)
{
break;
}
totalReceived += bytes;
}
}
catch (IOException)
{
// This is expected given an abort.
}
Assert.True(totalReceived < totalBytes, $"{nameof(AssertStreamAborted)} Stream completed successfully.");
}
private async Task AssertStreamCompleted(Stream stream, long minimumBytes, int targetBytesPerSecond)
{
var receiveBuffer = new byte[64 * 1024];
var received = 0;
var totalReceived = 0;
var startTime = DateTimeOffset.UtcNow;
do
{
received = await stream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length);
totalReceived += received;
var expectedTimeElapsed = TimeSpan.FromSeconds(totalReceived / targetBytesPerSecond);
var timeElapsed = DateTimeOffset.UtcNow - startTime;
if (timeElapsed < expectedTimeElapsed)
{
await Task.Delay(expectedTimeElapsed - timeElapsed);
}
} while (received > 0);
Assert.True(totalReceived >= minimumBytes, $"{nameof(AssertStreamCompleted)} Stream aborted prematurely.");
}
public static TheoryData<string, StringValues, string> NullHeaderData
{
get
{
var dataset = new TheoryData<string, StringValues, string>();
// Unknown headers
dataset.Add("NullString", (string)null, null);
dataset.Add("EmptyString", "", "");
dataset.Add("NullStringArray", new string[] { null }, null);
dataset.Add("EmptyStringArray", new string[] { "" }, "");
dataset.Add("MixedStringArray", new string[] { null, "" }, "");
// Known headers
dataset.Add("Location", (string)null, null);
dataset.Add("Location", "", "");
dataset.Add("Location", new string[] { null }, null);
dataset.Add("Location", new string[] { "" }, "");
dataset.Add("Location", new string[] { null, "" }, "");
return dataset;
}
}
}
}

View File

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Libuv.BindTests</AssemblyName>
<RootNamespace>Libuv.BindTests</RootNamespace>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
<ServerGarbageCollection>true</ServerGarbageCollection>
<TestGroupName>Libuv.BindTests</TestGroupName>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Kestrel.Transport.BindTests\**\*.cs" />
<Compile Include="..\Kestrel.Transport.Libuv.FunctionalTests\TransportSelector.cs" />
<Compile Include="..\shared\*.cs" LinkBase="shared" />
<Compile Include="..\shared\FunctionalTestHelpers\*.cs" LinkBase="shared\FunctionalTestHelpers" />
<Compile Include="..\shared\TransportTestHelpers\*.cs" LinkBase="shared\TransportTestHelpers" />
<Content Include="..\shared\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Kestrel\Kestrel.csproj" />
<ProjectReference Include="..\..\src\Kestrel.Transport.Libuv\Kestrel.Transport.Libuv.csproj" />
<ProjectReference Include="..\..\src\Kestrel.Https\Kestrel.Https.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -11,9 +11,11 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="..\shared\**\*.cs" />
<Compile Include="..\Kestrel.FunctionalTests\**\*.cs" />
<Content Include="..\shared\TestCertificates\*.pfx" CopyToOutputDirectory="PreserveNewest" />
<Compile Include="..\Kestrel.Transport.FunctionalTests\**\*.cs" />
<Compile Include="..\shared\*.cs" LinkBase="shared" />
<Compile Include="..\shared\FunctionalTestHelpers\*.cs" LinkBase="shared\FunctionalTestHelpers" />
<Compile Include="..\shared\TransportTestHelpers\*.cs" LinkBase="shared\TransportTestHelpers" />
<Content Include="..\shared\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>

View File

@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="..\shared\**\*.cs" />
<Compile Include="..\shared\*.cs" LinkBase="shared" />
</ItemGroup>
<ItemGroup>

View File

@ -1,7 +1,6 @@
// 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.Linq;
using System.Net;
@ -47,12 +46,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
await transport.StopAsync();
}
[Theory]
[MemberData(nameof(ConnectionAdapterData))]
public async Task TransportCanBindUnbindAndStop(ListenOptions listenOptions)
[Fact]
public async Task TransportCanBindUnbindAndStop()
{
var transportContext = new TestLibuvTransportContext();
var transport = new LibuvTransport(transportContext, listenOptions);
var transport = new LibuvTransport(transportContext, new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)));
await transport.BindAsync();
await transport.UnbindAsync();

View File

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Sockets.BindTests</AssemblyName>
<RootNamespace>Sockets.BindTests</RootNamespace>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
<ServerGarbageCollection>true</ServerGarbageCollection>
<TestGroupName>Sockets.BindTests</TestGroupName>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Kestrel.Transport.BindTests\**\*.cs" />
<Compile Include="..\Kestrel.Transport.Sockets.FunctionalTests\TransportSelector.cs" />
<Compile Include="..\shared\*.cs" LinkBase="shared" />
<Compile Include="..\shared\FunctionalTestHelpers\*.cs" LinkBase="shared\FunctionalTestHelpers" />
<Compile Include="..\shared\TransportTestHelpers\*.cs" LinkBase="shared\TransportTestHelpers" />
<Content Include="..\shared\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Kestrel\Kestrel.csproj" />
<ProjectReference Include="..\..\src\Kestrel.Transport.Sockets\Kestrel.Transport.Sockets.csproj" />
<ProjectReference Include="..\..\src\Kestrel.Https\Kestrel.Https.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -5,14 +5,16 @@
<RootNamespace>Sockets.FunctionalTests</RootNamespace>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
<DefineConstants Condition="$([MSBuild]::IsOSPlatform('OSX'))">$(DefineConstants);MACOS</DefineConstants>
<DefineConstants>$(DefineConstants);SOCKETS</DefineConstants>
<ServerGarbageCollection>true</ServerGarbageCollection>
<TestGroupName>Sockets.FunctionalTests</TestGroupName>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\shared\**\*.cs" />
<Compile Include="..\Kestrel.FunctionalTests\**\*.cs" />
<Content Include="..\shared\TestCertificates\*.pfx" CopyToOutputDirectory="PreserveNewest" />
<Compile Include="..\Kestrel.Transport.FunctionalTests\**\*.cs" />
<Compile Include="..\shared\*.cs" LinkBase="shared" />
<Compile Include="..\shared\FunctionalTestHelpers\*.cs" LinkBase="shared\FunctionalTestHelpers" />
<Compile Include="..\shared\TransportTestHelpers\*.cs" LinkBase="shared\TransportTestHelpers" />
<Content Include="..\shared\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,203 @@
// 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.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Testing
{
/// <summary>
/// Summary description for TestConnection
/// </summary>
public abstract class StreamBackedTestConnection : IDisposable
{
private static readonly TimeSpan Timeout = TimeSpan.FromMinutes(1);
private readonly Stream _stream;
private readonly StreamReader _reader;
protected StreamBackedTestConnection(Stream stream)
{
_stream = stream;
_reader = new StreamReader(_stream, Encoding.ASCII);
}
public Stream Stream => _stream;
public StreamReader Reader => _reader;
public abstract void ShutdownSend();
public abstract void Reset();
public virtual void Dispose()
{
_stream.Dispose();
}
public Task SendEmptyGet()
{
return Send("GET / HTTP/1.1",
"Host:",
"",
"");
}
public Task SendEmptyGetWithUpgradeAndKeepAlive()
=> SendEmptyGetWithConnection("Upgrade, keep-alive");
public Task SendEmptyGetWithUpgrade()
=> SendEmptyGetWithConnection("Upgrade");
public Task SendEmptyGetAsKeepAlive()
=> SendEmptyGetWithConnection("keep-alive");
private Task SendEmptyGetWithConnection(string connection)
{
return Send("GET / HTTP/1.1",
"Host:",
"Connection: " + connection,
"",
"");
}
public async Task SendAll(params string[] lines)
{
var text = string.Join("\r\n", lines);
var writer = new StreamWriter(_stream, Encoding.GetEncoding("iso-8859-1"));
await writer.WriteAsync(text).ConfigureAwait(false);
await writer.FlushAsync().ConfigureAwait(false);
await _stream.FlushAsync().ConfigureAwait(false);
}
public async Task Send(params string[] lines)
{
var text = string.Join("\r\n", lines);
var writer = new StreamWriter(_stream, Encoding.GetEncoding("iso-8859-1"));
for (var index = 0; index < text.Length; index++)
{
var ch = text[index];
writer.Write(ch);
await writer.FlushAsync().ConfigureAwait(false);
// Re-add delay to help find socket input consumption bugs more consistently
//await Task.Delay(TimeSpan.FromMilliseconds(5));
}
await writer.FlushAsync().ConfigureAwait(false);
await _stream.FlushAsync().ConfigureAwait(false);
}
public async Task Receive(params string[] lines)
{
var expected = string.Join("\r\n", lines);
var actual = new char[expected.Length];
var offset = 0;
try
{
while (offset < expected.Length)
{
var data = new byte[expected.Length];
var task = _reader.ReadAsync(actual, offset, actual.Length - offset);
if (!Debugger.IsAttached)
{
task = task.TimeoutAfter(Timeout);
}
var count = await task.ConfigureAwait(false);
if (count == 0)
{
break;
}
offset += count;
}
}
catch (TimeoutException ex) when (offset != 0)
{
throw new TimeoutException($"Did not receive a complete response within {Timeout}.{Environment.NewLine}{Environment.NewLine}" +
$"Expected:{Environment.NewLine}{expected}{Environment.NewLine}{Environment.NewLine}" +
$"Actual:{Environment.NewLine}{new string(actual, 0, offset)}{Environment.NewLine}",
ex);
}
Assert.Equal(expected, new string(actual, 0, offset));
}
public async Task ReceiveEnd(params string[] lines)
{
await Receive(lines).ConfigureAwait(false);
ShutdownSend();
var ch = new char[128];
var count = await _reader.ReadAsync(ch, 0, 128).TimeoutAfter(Timeout).ConfigureAwait(false);
var text = new string(ch, 0, count);
Assert.Equal("", text);
}
public async Task ReceiveForcedEnd(params string[] lines)
{
await Receive(lines).ConfigureAwait(false);
try
{
var ch = new char[128];
var count = await _reader.ReadAsync(ch, 0, 128).TimeoutAfter(Timeout).ConfigureAwait(false);
var text = new string(ch, 0, count);
Assert.Equal("", text);
}
catch (IOException)
{
// The server is forcefully closing the connection so an IOException:
// "Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host."
// isn't guaranteed but not unexpected.
}
}
public async Task ReceiveStartsWith(string prefix, int maxLineLength = 1024)
{
var actual = new char[maxLineLength];
var offset = 0;
while (offset < maxLineLength)
{
// Read one char at a time so we don't read past the end of the line.
var task = _reader.ReadAsync(actual, offset, 1);
if (!Debugger.IsAttached)
{
task = task.TimeoutAfter(Timeout);
}
var count = await task.ConfigureAwait(false);
if (count == 0)
{
break;
}
Assert.True(count == 1);
offset++;
if (actual[offset - 1] == '\n')
{
break;
}
}
var actualLine = new string(actual, 0, offset);
Assert.StartsWith(prefix, actualLine);
}
public async Task WaitForConnectionClose()
{
var buffer = new byte[128];
var bytesTransferred = await _stream.ReadAsync(buffer, 0, 128).TimeoutAfter(Timeout);
if (bytesTransferred > 0)
{
throw new IOException(
$"Expected connection close, received data instead: \"{_reader.CurrentEncoding.GetString(buffer, 0, bytesTransferred)}\"");
}
}
}
}

View File

@ -2,27 +2,17 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Testing
{
/// <summary>
/// Summary description for TestConnection
/// </summary>
public class TestConnection : IDisposable
public class TestConnection : StreamBackedTestConnection
{
private static readonly TimeSpan Timeout = TimeSpan.FromMinutes(1);
private readonly bool _ownsSocket;
private readonly Socket _socket;
private readonly NetworkStream _stream;
private readonly StreamReader _reader;
public TestConnection(int port)
: this(port, AddressFamily.InterNetwork)
@ -40,217 +30,29 @@ namespace Microsoft.AspNetCore.Testing
}
private TestConnection(Socket socket, bool ownsSocket)
: base(new NetworkStream(socket, ownsSocket: ownsSocket))
{
_ownsSocket = ownsSocket;
_socket = socket;
_stream = new NetworkStream(_socket, ownsSocket: false);
_reader = new StreamReader(_stream, Encoding.ASCII);
}
public Socket Socket => _socket;
public Stream Stream => _stream;
public StreamReader Reader => _reader;
public void Dispose()
{
_stream.Dispose();
if (_ownsSocket)
{
_socket.Dispose();
}
}
public Task SendEmptyGet()
{
return Send("GET / HTTP/1.1",
"Host:",
"",
"");
}
public Task SendEmptyGetWithUpgradeAndKeepAlive()
=> SendEmptyGetWithConnection("Upgrade, keep-alive");
public Task SendEmptyGetWithUpgrade()
=> SendEmptyGetWithConnection("Upgrade");
public Task SendEmptyGetAsKeepAlive()
=> SendEmptyGetWithConnection("keep-alive");
private Task SendEmptyGetWithConnection(string connection)
{
return Send("GET / HTTP/1.1",
"Host:",
"Connection: " + connection,
"",
"");
}
public async Task SendAll(params string[] lines)
{
var text = string.Join("\r\n", lines);
var writer = new StreamWriter(_stream, Encoding.GetEncoding("iso-8859-1"));
await writer.WriteAsync(text).ConfigureAwait(false);
await writer.FlushAsync().ConfigureAwait(false);
await _stream.FlushAsync().ConfigureAwait(false);
}
public async Task Send(params string[] lines)
{
var text = string.Join("\r\n", lines);
var writer = new StreamWriter(_stream, Encoding.GetEncoding("iso-8859-1"));
for (var index = 0; index < text.Length; index++)
{
var ch = text[index];
writer.Write(ch);
await writer.FlushAsync().ConfigureAwait(false);
// Re-add delay to help find socket input consumption bugs more consistently
//await Task.Delay(TimeSpan.FromMilliseconds(5));
}
await writer.FlushAsync().ConfigureAwait(false);
await _stream.FlushAsync().ConfigureAwait(false);
}
public async Task Receive(params string[] lines)
{
var expected = string.Join("\r\n", lines);
var actual = new char[expected.Length];
var offset = 0;
try
{
while (offset < expected.Length)
{
var data = new byte[expected.Length];
var task = _reader.ReadAsync(actual, offset, actual.Length - offset);
if (!Debugger.IsAttached)
{
task = task.TimeoutAfter(Timeout);
}
var count = await task.ConfigureAwait(false);
if (count == 0)
{
break;
}
offset += count;
}
}
catch (TimeoutException ex) when (offset != 0)
{
throw new TimeoutException($"Did not receive a complete response within {Timeout}.{Environment.NewLine}{Environment.NewLine}" +
$"Expected:{Environment.NewLine}{expected}{Environment.NewLine}{Environment.NewLine}" +
$"Actual:{Environment.NewLine}{new string(actual, 0, offset)}{Environment.NewLine}",
ex);
}
Assert.Equal(expected, new string(actual, 0, offset));
}
public async Task ReceiveEnd(params string[] lines)
{
await Receive(lines).ConfigureAwait(false);
_socket.Shutdown(SocketShutdown.Send);
var ch = new char[128];
var count = await _reader.ReadAsync(ch, 0, 128).TimeoutAfter(Timeout).ConfigureAwait(false);
var text = new string(ch, 0, count);
Assert.Equal("", text);
}
public async Task ReceiveForcedEnd(params string[] lines)
{
await Receive(lines).ConfigureAwait(false);
try
{
var ch = new char[128];
var count = await _reader.ReadAsync(ch, 0, 128).TimeoutAfter(Timeout).ConfigureAwait(false);
var text = new string(ch, 0, count);
Assert.Equal("", text);
}
catch (IOException)
{
// The server is forcefully closing the connection so an IOException:
// "Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host."
// isn't guaranteed but not unexpected.
}
}
public async Task ReceiveStartsWith(string prefix, int maxLineLength = 1024)
{
var actual = new char[maxLineLength];
var offset = 0;
while (offset < maxLineLength)
{
// Read one char at a time so we don't read past the end of the line.
var task = _reader.ReadAsync(actual, offset, 1);
if (!Debugger.IsAttached)
{
Assert.True(task.Wait(4000), "timeout");
}
var count = await task.ConfigureAwait(false);
if (count == 0)
{
break;
}
Assert.True(count == 1);
offset++;
if (actual[offset - 1] == '\n')
{
break;
}
}
var actualLine = new string(actual, 0, offset);
Assert.StartsWith(prefix, actualLine);
}
public void Shutdown(SocketShutdown how)
{
_socket.Shutdown(how);
}
public void Reset()
public override void ShutdownSend()
{
Shutdown(SocketShutdown.Send);
}
public override void Reset()
{
_socket.LingerState = new LingerOption(true, 0);
_socket.Dispose();
}
public Task WaitForConnectionClose()
{
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var eventArgs = new SocketAsyncEventArgs();
eventArgs.SetBuffer(new byte[128], 0, 128);
eventArgs.Completed += ReceiveAsyncCompleted;
eventArgs.UserToken = tcs;
if (!_socket.ReceiveAsync(eventArgs))
{
ReceiveAsyncCompleted(this, eventArgs);
}
return tcs.Task;
}
private void ReceiveAsyncCompleted(object sender, SocketAsyncEventArgs e)
{
var tcs = (TaskCompletionSource<object>)e.UserToken;
if (e.BytesTransferred == 0)
{
tcs.SetResult(null);
}
else
{
tcs.SetException(new IOException(
$"Expected connection close, received data instead: \"{_reader.CurrentEncoding.GetString(e.Buffer, 0, e.BytesTransferred)}\""));
}
}
public static Socket CreateConnectedLoopbackSocket(int port) => CreateConnectedLoopbackSocket(port, AddressFamily.InterNetwork);
public static Socket CreateConnectedLoopbackSocket(int port, AddressFamily addressFamily)

View File

@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Testing
{
public static class TestResources
{
private static readonly string _baseDir = Directory.GetCurrentDirectory();
private static readonly string _baseDir = Path.Combine(Directory.GetCurrentDirectory(), "shared", "TestCertificates");
public static string TestCertificatePath { get; } = Path.Combine(_baseDir, "testCert.pfx");
public static string GetCertPath(string name) => Path.Combine(_baseDir, name);

View File

@ -1,11 +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.Buffers;
using System.IO.Pipelines;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Testing
@ -36,13 +39,28 @@ namespace Microsoft.AspNetCore.Testing
return new KestrelTrace(loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel"));
}
public void InitializeHeartbeat()
{
MockSystemClock = null;
SystemClock = new SystemClock();
DateHeaderValueManager = new DateHeaderValueManager(SystemClock);
var heartbeatManager = new HttpHeartbeatManager(ConnectionManager);
Heartbeat = new Heartbeat(
new IHeartbeatHandler[] { DateHeaderValueManager, heartbeatManager },
SystemClock,
DebuggerWrapper.Singleton,
Log);
}
private void Initialize(ILoggerFactory loggerFactory, IKestrelTrace kestrelTrace)
{
LoggerFactory = loggerFactory;
Log = kestrelTrace;
Scheduler = PipeScheduler.ThreadPool;
SystemClock = new MockSystemClock();
DateHeaderValueManager = new DateHeaderValueManager(SystemClock);
MockSystemClock = new MockSystemClock();
SystemClock = MockSystemClock;
DateHeaderValueManager = new DateHeaderValueManager(MockSystemClock);
ConnectionManager = new HttpConnectionManager(Log, ResourceCounter.Unlimited);
HttpParser = new HttpParser<Http1ParsingHandler>(Log.IsEnabled(LogLevel.Information));
ServerOptions = new KestrelServerOptions
@ -53,6 +71,10 @@ namespace Microsoft.AspNetCore.Testing
public ILoggerFactory LoggerFactory { get; set; }
public MockSystemClock MockSystemClock { get; set; }
public Func<MemoryPool<byte>> MemoryPoolFactory { get; set; } = KestrelMemoryPool.Create;
public string DateHeaderValue => DateHeaderValueManager.GetDateHeaderValues().String;
}
}

View File

@ -44,20 +44,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
public TestServer(RequestDelegate app, TestServiceContext context, ListenOptions listenOptions, Action<IServiceCollection> configureServices)
: this(TransportSelector.GetWebHostBuilder(), app, context, options => options.ListenOptions.Add(listenOptions), configureServices)
: this(app, context, options => options.ListenOptions.Add(listenOptions), configureServices)
{
}
public TestServer(RequestDelegate app, TestServiceContext context, Action<KestrelServerOptions> configureKestrel)
: this(TransportSelector.GetWebHostBuilder(), app, context, configureKestrel, _ => { })
: this(app, context, configureKestrel, _ => { })
{
}
public TestServer(IWebHostBuilder builder, RequestDelegate app, TestServiceContext context, Action<KestrelServerOptions> configureKestrel, Action<IServiceCollection> configureServices)
public TestServer(RequestDelegate app, TestServiceContext context, Action<KestrelServerOptions> configureKestrel, Action<IServiceCollection> configureServices)
{
_app = app;
Context = context;
_host = builder
_host = TransportSelector.GetWebHostBuilder(context.MemoryPoolFactory)
.UseKestrel(options =>
{
configureKestrel(options);