Merge pull request #1524 from dotnet-maestro-bot/merge/release/2.2-to-master

[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
Pavel Krymets 2018-10-18 11:18:52 -07:00 committed by GitHub
commit f2b4abee02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 166 additions and 73 deletions

View File

@ -64,8 +64,9 @@
<Category>noship</Category> <Category>noship</Category>
</ArtifactInfo> </ArtifactInfo>
<FilesToSign Include="$(AncmZipOutputPath)" IsContainer="true" /> <FilesToSign Include="$(AncmZipOutputPath)" Certificate="None" IsContainer="true" />
<FilesToExcludeFromSigning Include="$(StressTestWebSiteZipOutputPath)" /> <FilesToExcludeFromSigning Include="$(StressTestWebSiteZipOutputPath)" />
<FilesToExcludeFromSigning Include="AspNetCore\ancm.mof" Container="$(BuildDir)Microsoft.AspNetCore.AspNetCoreModuleV2.$(PackageVersion).nupkg" />
</ItemGroup> </ItemGroup>
</Target> </Target>

View File

@ -1,2 +1,2 @@
version:3.0.0-alpha1-20181011.11 version:3.0.0-alpha1-20181011.11
commithash:f57aa8ddda0abdd74ada55853587bedb4f364065 commithash:f57aa8ddda0abdd74ada55853587bedb4f364065

View File

@ -14,8 +14,8 @@
"commandLineArgs": "$(IISExpressArguments)", "commandLineArgs": "$(IISExpressArguments)",
"environmentVariables": { "environmentVariables": {
"IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)",
"ANCM_PATH": "$(AncmPath)", "ANCM_PATH": "$(AspNetCoreModuleV1ShimDll)",
"ANCMV2_PATH": "$(AncmV2Path)", "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)",
"ANCM_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", "ANCM_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)",
"LAUNCHER_ARGS": "$(TargetPath)", "LAUNCHER_ARGS": "$(TargetPath)",
"ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_ENVIRONMENT": "Development",
@ -29,9 +29,9 @@
"commandLineArgs": "$(IISArguments)", "commandLineArgs": "$(IISArguments)",
"environmentVariables": { "environmentVariables": {
"IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)",
"ANCM_PATH": "$(AncmPath)", "ANCM_PATH": "$(AspNetCoreModuleV1ShimDll)",
"ANCMV2_PATH": "$(AncmV2Path)", "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)",
"ANCM_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", "ASPNETCORE_MODULE_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)",
"LAUNCHER_ARGS": "$(TargetPath)", "LAUNCHER_ARGS": "$(TargetPath)",
"ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_ENVIRONMENT": "Development",
"LAUNCHER_PATH": "$(DotNetPath)", "LAUNCHER_PATH": "$(DotNetPath)",

View File

@ -6,6 +6,7 @@ using System.Buffers;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.IIS.Core namespace Microsoft.AspNetCore.Server.IIS.Core
{ {
@ -121,6 +122,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
catch (Exception ex) catch (Exception ex)
{ {
error = ex; error = ex;
Log.UnexpectedError(_logger, nameof(IISHttpContext), ex);
} }
finally finally
{ {
@ -174,6 +176,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
catch (Exception ex) catch (Exception ex)
{ {
error = ex; error = ex;
Log.UnexpectedError(_logger, nameof(IISHttpContext), ex);
} }
finally finally
{ {
@ -199,9 +202,9 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{ {
cts.Cancel(); cts.Cancel();
} }
catch (Exception) catch (Exception ex)
{ {
// ignore Log.ApplicationError(_logger, ((IHttpConnectionFeature)this).ConnectionId, TraceIdentifier, ex);
} }
}); });
} }
@ -219,6 +222,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
internal void ConnectionReset() internal void ConnectionReset()
{ {
AbortIO(); AbortIO();
Log.ConnectionDisconnect(_logger, ((IHttpConnectionFeature)this).ConnectionId);
} }
} }
} }

View File

@ -0,0 +1,39 @@
// 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.Runtime.CompilerServices;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
internal abstract partial class IISHttpContext
{
private static class Log
{
private static readonly Action<ILogger, string, Exception> _connectionDisconnect =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(1, "ConnectionDisconnect"), @"Connection ID ""{ConnectionId}"" disconnecting.");
private static readonly Action<ILogger, string, string, Exception> _applicationError =
LoggerMessage.Define<string, string>(LogLevel.Error, new EventId(2, "ApplicationError"), @"Connection ID ""{ConnectionId}"", Request ID ""{TraceIdentifier}"": An unhandled exception was thrown by the application.");
private static readonly Action<ILogger, string, string, Exception> _unexpectedError =
LoggerMessage.Define<string, string>(LogLevel.Error, new EventId(3, "UnexpectedError"), @"Unexpected exception in ""{ClassName}.{MethodName}"".");
public static void ConnectionDisconnect(ILogger logger, string connectionId)
{
_connectionDisconnect(logger, connectionId, null);
}
public static void ApplicationError(ILogger logger, string connectionId, string traceIdentifier, Exception ex)
{
_applicationError(logger, connectionId, traceIdentifier, ex);
}
public static void UnexpectedError(ILogger logger, string className, Exception ex, [CallerMemberName] string methodName = null)
{
_unexpectedError(logger, className, methodName, ex);
}
}
}
}

View File

@ -17,9 +17,11 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.HttpSys.Internal; using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.AspNetCore.Server.IIS.Core.IO; using Microsoft.AspNetCore.Server.IIS.Core.IO;
using Microsoft.AspNetCore.WebUtilities; using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.IIS.Core namespace Microsoft.AspNetCore.Server.IIS.Core
{ {
@ -50,6 +52,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
private readonly MemoryPool<byte> _memoryPool; private readonly MemoryPool<byte> _memoryPool;
private readonly IISHttpServer _server; private readonly IISHttpServer _server;
private readonly ILogger _logger;
private GCHandle _thisHandle; private GCHandle _thisHandle;
protected Task _readBodyTask; protected Task _readBodyTask;
protected Task _writeBodyTask; protected Task _writeBodyTask;
@ -69,13 +73,15 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
MemoryPool<byte> memoryPool, MemoryPool<byte> memoryPool,
IntPtr pInProcessHandler, IntPtr pInProcessHandler,
IISServerOptions options, IISServerOptions options,
IISHttpServer server) IISHttpServer server,
ILogger logger)
: base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.HttpGetRawRequest(pInProcessHandler)) : base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.HttpGetRawRequest(pInProcessHandler))
{ {
_memoryPool = memoryPool; _memoryPool = memoryPool;
_pInProcessHandler = pInProcessHandler; _pInProcessHandler = pInProcessHandler;
_options = options; _options = options;
_server = server; _server = server;
_logger = logger;
} }
public Version HttpVersion { get; set; } public Version HttpVersion { get; set; }
@ -450,6 +456,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{ {
_applicationException = new AggregateException(_applicationException, ex); _applicationException = new AggregateException(_applicationException, ex);
} }
Log.ApplicationError(_logger, ((IHttpConnectionFeature)this).ConnectionId, TraceIdentifier, ex);
} }
public void PostCompletion(NativeMethods.REQUEST_NOTIFICATION_STATUS requestNotificationStatus) public void PostCompletion(NativeMethods.REQUEST_NOTIFICATION_STATUS requestNotificationStatus)

View File

@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.IIS.Core namespace Microsoft.AspNetCore.Server.IIS.Core
{ {
@ -14,8 +15,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{ {
private readonly IHttpApplication<TContext> _application; private readonly IHttpApplication<TContext> _application;
public IISHttpContextOfT(MemoryPool<byte> memoryPool, IHttpApplication<TContext> application, IntPtr pInProcessHandler, IISServerOptions options, IISHttpServer server) public IISHttpContextOfT(MemoryPool<byte> memoryPool, IHttpApplication<TContext> application, IntPtr pInProcessHandler, IISServerOptions options, IISHttpServer server, ILogger logger)
: base(memoryPool, pInProcessHandler, options, server) : base(memoryPool, pInProcessHandler, options, server, logger)
{ {
_application = application; _application = application;
} }

View File

@ -82,7 +82,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{ {
_httpServerHandle = GCHandle.Alloc(this); _httpServerHandle = GCHandle.Alloc(this);
_iisContextFactory = new IISContextFactory<TContext>(_memoryPool, application, _options, this); _iisContextFactory = new IISContextFactory<TContext>(_memoryPool, application, _options, this, _logger);
_nativeApplication.RegisterCallbacks(_requestHandler, _shutdownHandler, _onDisconnect, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle); _nativeApplication.RegisterCallbacks(_requestHandler, _shutdownHandler, _onDisconnect, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle);
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -260,18 +260,20 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
private readonly MemoryPool<byte> _memoryPool; private readonly MemoryPool<byte> _memoryPool;
private readonly IISServerOptions _options; private readonly IISServerOptions _options;
private readonly IISHttpServer _server; private readonly IISHttpServer _server;
private readonly ILogger _logger;
public IISContextFactory(MemoryPool<byte> memoryPool, IHttpApplication<T> application, IISServerOptions options, IISHttpServer server) public IISContextFactory(MemoryPool<byte> memoryPool, IHttpApplication<T> application, IISServerOptions options, IISHttpServer server, ILogger logger)
{ {
_application = application; _application = application;
_memoryPool = memoryPool; _memoryPool = memoryPool;
_options = options; _options = options;
_server = server; _server = server;
_logger = logger;
} }
public IISHttpContext CreateHttpContext(IntPtr pInProcessHandler) public IISHttpContext CreateHttpContext(IntPtr pInProcessHandler)
{ {
return new IISHttpContextOfT<T>(_memoryPool, _application, pInProcessHandler, _options, _server); return new IISHttpContextOfT<T>(_memoryPool, _application, pInProcessHandler, _options, _server, _logger);
} }
} }
} }

View File

@ -11,68 +11,57 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
public class ClientCertificateFixture : IDisposable public class ClientCertificateFixture : IDisposable
{ {
private X509Certificate2 _certificate; private X509Certificate2 _certificate;
private const string _certIssuerPrefix = "CN=IISIntegrationTest_Root";
public X509Certificate2 Certificate public X509Certificate2 GetOrCreateCertificate()
{ {
get if (_certificate != null)
{ {
if (_certificate != null) return _certificate;
{ }
return _certificate;
}
using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine)) using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
{ {
store.Open(OpenFlags.ReadWrite); store.Open(OpenFlags.ReadWrite);
var parentKey = CreateKeyMaterial(2048);
foreach (var cert in store.Certificates) // Create a cert name with a random guid to avoid name conflicts
{ var parentRequest = new CertificateRequest(
if (cert.Issuer != "CN=IISIntegrationTest_Root") _certIssuerPrefix + Guid.NewGuid().ToString(),
{ parentKey, HashAlgorithmName.SHA256,
continue; RSASignaturePadding.Pkcs1);
}
_certificate = cert;
store.Close();
return cert;
}
var parentKey = CreateKeyMaterial(2048); parentRequest.CertificateExtensions.Add(
new X509BasicConstraintsExtension(
certificateAuthority: true,
hasPathLengthConstraint: false,
pathLengthConstraint: 0,
critical: true));
// On first run of the test, creates the certificate in the trusted root certificate authorities. parentRequest.CertificateExtensions.Add(
var parentRequest = new CertificateRequest("CN=IISIntegrationTest_Root", parentKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation, critical: true));
parentRequest.CertificateExtensions.Add( parentRequest.CertificateExtensions.Add(
new X509BasicConstraintsExtension( new X509SubjectKeyIdentifierExtension(parentRequest.PublicKey, false));
certificateAuthority: true,
hasPathLengthConstraint: false,
pathLengthConstraint: 0,
critical: true));
parentRequest.CertificateExtensions.Add( var notBefore = DateTimeOffset.Now.AddDays(-1);
new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation, critical: true)); var notAfter = DateTimeOffset.Now.AddYears(5);
parentRequest.CertificateExtensions.Add( var parentCert = parentRequest.CreateSelfSigned(notBefore, notAfter);
new X509SubjectKeyIdentifierExtension(parentRequest.PublicKey, false));
var notBefore = DateTimeOffset.Now.AddDays(-1); // Need to export/import the certificate to associate the private key with the cert.
var notAfter = DateTimeOffset.Now.AddYears(5); var imported = parentCert;
var parentCert = parentRequest.CreateSelfSigned(notBefore, notAfter); var export = parentCert.Export(X509ContentType.Pkcs12, "");
imported = new X509Certificate2(export, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
Array.Clear(export, 0, export.Length);
// Need to export/import the certificate to associate the private key with the cert. // Add the cert to the cert store
var imported = parentCert; _certificate = imported;
var export = parentCert.Export(X509ContentType.Pkcs12, ""); store.Add(certificate: imported);
imported = new X509Certificate2(export, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); store.Close();
Array.Clear(export, 0, export.Length); return imported;
// Add the cert to the cert store
_certificate = imported;
store.Add(certificate: imported);
store.Close();
return imported;
}
} }
} }
@ -86,7 +75,17 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine)) using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
{ {
store.Open(OpenFlags.ReadWrite); store.Open(OpenFlags.ReadWrite);
store.Remove(Certificate); store.Remove(_certificate);
// Remove any extra certs that were left by previous tests.
for (var i = store.Certificates.Count - 1; i >= 0; i--)
{
var cert = store.Certificates[i];
if (cert.Issuer.StartsWith(_certIssuerPrefix))
{
store.Remove(cert);
}
}
store.Close(); store.Close();
} }
} }

View File

@ -1,7 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net.Http; using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities;
using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests; using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests;
@ -9,6 +11,7 @@ using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Server.IntegrationTesting.Common; using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
@ -54,31 +57,51 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
deploymentParameters.ApplicationBaseUriHint = $"https://localhost:{port}/"; deploymentParameters.ApplicationBaseUriHint = $"https://localhost:{port}/";
deploymentParameters.AddHttpsToServerConfig(); deploymentParameters.AddHttpsToServerConfig();
var deploymentResult = await DeployAsync(deploymentParameters);
var handler = new HttpClientHandler var handler = new HttpClientHandler
{ {
ServerCertificateCustomValidationCallback = (a, b, c, d) => true, ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
ClientCertificateOptions = ClientCertificateOption.Manual, ClientCertificateOptions = ClientCertificateOption.Manual,
}; };
X509Certificate2 cert = null;
if (sendClientCert) if (sendClientCert)
{ {
Assert.NotNull(_certFixture.Certificate); cert = _certFixture.GetOrCreateCertificate();
handler.ClientCertificates.Add(_certFixture.Certificate); handler.ClientCertificates.Add(cert);
} }
var deploymentResult = await DeployAsync(deploymentParameters);
var client = deploymentResult.CreateClient(handler); var client = deploymentResult.CreateClient(handler);
var response = await client.GetAsync("GetClientCert"); var response = await client.GetAsync("GetClientCert");
var responseText = await response.Content.ReadAsStringAsync(); var responseText = await response.Content.ReadAsStringAsync();
if (sendClientCert) try
{ {
Assert.Equal($"Enabled;{_certFixture.Certificate.GetCertHashString()}", responseText); if (sendClientCert)
{
Assert.Equal($"Enabled;{cert.GetCertHashString()}", responseText);
}
else
{
Assert.Equal("Disabled", responseText);
}
} }
else catch (Exception ex)
{ {
Assert.Equal("Disabled", responseText); Logger.LogError($"Certificate is invalid. Issuer name: {cert.Issuer}");
using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
{
Logger.LogError($"List of current certificates in root store:");
store.Open(OpenFlags.ReadWrite);
foreach (var otherCert in store.Certificates)
{
Logger.LogError(otherCert.Issuer);
}
store.Close();
}
throw ex;
} }
} }
} }

View File

@ -17,7 +17,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")]
public class ClientDisconnectTests : StrictTestServerTests public class ClientDisconnectTests : StrictTestServerTests
{ {
[ConditionalFact] [ConditionalFact]
public async Task WritesSucceedAfterClientDisconnect() public async Task WritesSucceedAfterClientDisconnect()
{ {
@ -48,6 +47,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
await requestCompletedCompletionSource.Task.DefaultTimeout(); await requestCompletedCompletionSource.Task.DefaultTimeout();
} }
AssertConnectionDisconnectLog();
} }
[ConditionalFact] [ConditionalFact]
@ -88,6 +89,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Assert.IsType<OperationCanceledException>(exception); Assert.IsType<OperationCanceledException>(exception);
} }
AssertConnectionDisconnectLog();
} }
[ConditionalFact] [ConditionalFact]
@ -125,6 +128,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Assert.IsType<ConnectionResetException>(exception); Assert.IsType<ConnectionResetException>(exception);
Assert.Equal("The client has disconnected", exception.Message); Assert.Equal("The client has disconnected", exception.Message);
AssertConnectionDisconnectLog();
} }
[ConditionalFact] [ConditionalFact]
@ -202,6 +207,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
} }
Assert.IsType<OperationCanceledException>(exception); Assert.IsType<OperationCanceledException>(exception);
} }
AssertConnectionDisconnectLog();
} }
[ConditionalFact] [ConditionalFact]
@ -253,6 +260,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Assert.IsType<ConnectionResetException>(exception); Assert.IsType<ConnectionResetException>(exception);
Assert.Equal("The client has disconnected", exception.Message); Assert.Equal("The client has disconnected", exception.Message);
AssertConnectionDisconnectLog();
} }
[ConditionalFact] [ConditionalFact]
@ -275,7 +283,15 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
} }
await requestAborted.Task; await requestAborted.Task;
} }
AssertConnectionDisconnectLog();
} }
private void AssertConnectionDisconnectLog()
{
Assert.Contains(TestSink.Writes, w => w.EventId.Name == "ConnectionDisconnect");
}
private static async Task SendContentLength1Post(TestConnection connection) private static async Task SendContentLength1Post(TestConnection connection)
{ {
await connection.Send( await connection.Send(