From 194059a198d843ede2b96b067b2b50c4e602c8d9 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Fri, 21 Oct 2016 16:22:13 -0700 Subject: [PATCH] Implement Begin/End Read/Write methods in LoggingStream - This allows the reads and writes from SslStream to be logged on desktop .NET --- .../Filter/Internal/LoggingStream.cs | 80 +++++++++++++++++++ .../LoggingConnectionFilterTests.cs | 47 +++++++++++ .../ChunkedRequestTests.cs | 1 + .../ChunkedResponseTests.cs | 1 + .../ConnectionFilterTests.cs | 19 ++--- .../EngineTests.cs | 1 + .../PassThroughConnectionFilter.cs | 2 +- 7 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/LoggingConnectionFilterTests.cs diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Filter/Internal/LoggingStream.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Filter/Internal/LoggingStream.cs index e43d1c7f2d..14890c0e3a 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Filter/Internal/LoggingStream.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Filter/Internal/LoggingStream.cs @@ -1,6 +1,7 @@ // 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.Text; using System.Threading; @@ -125,5 +126,84 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Filter.Internal _logger.LogDebug(builder.ToString()); } + +#if NET451 + // The below APM methods call the underlying Read/WriteAsync methods which will still be logged. + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + var task = ReadAsync(buffer, offset, count, default(CancellationToken), state); + if (callback != null) + { + task.ContinueWith(t => callback.Invoke(t)); + } + return task; + } + + public override int EndRead(IAsyncResult asyncResult) + { + return ((Task)asyncResult).GetAwaiter().GetResult(); + } + + private Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) + { + var tcs = new TaskCompletionSource(state); + var task = ReadAsync(buffer, offset, count, cancellationToken); + task.ContinueWith((task2, state2) => + { + var tcs2 = (TaskCompletionSource)state2; + if (task2.IsCanceled) + { + tcs2.SetCanceled(); + } + else if (task2.IsFaulted) + { + tcs2.SetException(task2.Exception); + } + else + { + tcs2.SetResult(task2.Result); + } + }, tcs, cancellationToken); + return tcs.Task; + } + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + var task = WriteAsync(buffer, offset, count, default(CancellationToken), state); + if (callback != null) + { + task.ContinueWith(t => callback.Invoke(t)); + } + return task; + } + + public override void EndWrite(IAsyncResult asyncResult) + { + ((Task)asyncResult).GetAwaiter().GetResult(); + } + + private Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) + { + var tcs = new TaskCompletionSource(state); + var task = WriteAsync(buffer, offset, count, cancellationToken); + task.ContinueWith((task2, state2) => + { + var tcs2 = (TaskCompletionSource)state2; + if (task2.IsCanceled) + { + tcs2.SetCanceled(); + } + else if (task2.IsFaulted) + { + tcs2.SetException(task2.Exception); + } + else + { + tcs2.SetResult(null); + } + }, tcs, cancellationToken); + return tcs.Task; + } +#endif } } diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/LoggingConnectionFilterTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/LoggingConnectionFilterTests.cs new file mode 100644 index 0000000000..6dd7a8fb98 --- /dev/null +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/LoggingConnectionFilterTests.cs @@ -0,0 +1,47 @@ +// 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.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests +{ + public class LoggingConnectionFilterTests + { + [Fact] + public async Task LoggingConnectionFilterCanBeAddedBeforeAndAfterHttpsFilter() + { + var host = new WebHostBuilder() + .UseUrls($"https://127.0.0.1:0") + .UseKestrel(options => + { + options.UseConnectionLogging(); + options.UseHttps(@"TestResources/testCert.pfx", "testPassword"); + }) + .Configure(app => + { + app.Run(context => + { + context.Response.ContentLength = 12; + return context.Response.WriteAsync("Hello World!"); + }); + }) + .Build(); + + using (host) + { + host.Start(); + + var response = await HttpClientSlim.GetStringAsync($"https://localhost:{host.GetPort()}/", validateCertificate: false) + .TimeoutAfter(TimeSpan.FromSeconds(10)); + + Assert.Equal("Hello World!", response); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedRequestTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedRequestTests.cs index 30f4ecd9d6..403e7a0208 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedRequestTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedRequestTests.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Internal; using Xunit; diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedResponseTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedResponseTests.cs index b6e59342fb..469f17051c 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedResponseTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedResponseTests.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers; using Microsoft.AspNetCore.Testing; using Xunit; diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/ConnectionFilterTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/ConnectionFilterTests.cs index 3d1423dc2b..ea76646561 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/ConnectionFilterTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/ConnectionFilterTests.cs @@ -88,19 +88,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { using (var connection = server.CreateConnection()) { - try - { - await connection.SendEnd( - "POST / HTTP/1.0", - "Content-Length: 12", - "", - "Hello World?"); - } - catch (IOException) - { - // Will throw because the exception in the connection filter will close the connection. - Assert.True(true); - } + // Will throw because the exception in the connection filter will close the connection. + await Assert.ThrowsAsync(async () => await connection.SendEnd( + "POST / HTTP/1.0", + "Content-Length: 12", + "", + "Hello World?")); } } } diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs index ef73602834..650fcedbe1 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel.Internal; using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; +using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Internal; using Xunit; diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/TestHelpers/PassThroughConnectionFilter.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/TestHelpers/PassThroughConnectionFilter.cs index bab4e24987..b4492bb8b6 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/TestHelpers/PassThroughConnectionFilter.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/TestHelpers/PassThroughConnectionFilter.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Filter.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Internal; -namespace Microsoft.AspNetCore.Server.KestrelTests +namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers { public class PassThroughConnectionFilter : IConnectionFilter