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:
parent
b494e50353
commit
0e99235d59
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -90,5 +90,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
|||
_handshakeTimeout = value != Timeout.InfiniteTimeSpan ? value : TimeSpan.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
// For testing
|
||||
internal Action OnHandshakeStarted;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Libuv.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Libuv.BindTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Sockets.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Sockets.BindTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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[]
|
||||
{
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
@ -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())
|
||||
{
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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())
|
||||
{
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
@ -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>(() =>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -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(
|
||||
|
|
@ -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]
|
||||
|
|
@ -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(
|
||||
|
|
@ -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(
|
||||
|
|
@ -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",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
File diff suppressed because it is too large
Load Diff
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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++)
|
||||
{
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -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'">
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\shared\**\*.cs" />
|
||||
<Compile Include="..\shared\*.cs" LinkBase="shared" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
Loading…
Reference in New Issue