Add unit tests for ConectionFilters and HTTPS

- Run all the EngineTests with and without a ConnectionFilter
This commit is contained in:
Stephen Halter 2015-09-30 16:31:16 -07:00
parent 2f3a00625a
commit 0844369f5f
8 changed files with 413 additions and 84 deletions

View File

@ -6,6 +6,12 @@ using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.Extensions.Logging;
#if DNX451
using System.IO;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNet.Server.Kestrel.Https;
#endif
namespace SampleApp
{
public class Startup
@ -16,6 +22,21 @@ namespace SampleApp
loggerFactory.AddConsole(LogLevel.Debug);
#if DNX451
var testCertPath = Path.Combine(
Environment.CurrentDirectory,
@"../../test/Microsoft.AspNet.Server.KestrelTests/TestResources/testCert.cer");
if (File.Exists(testCertPath))
{
app.UseKestrelHttps(new X509Certificate2(testCertPath));
}
else
{
Console.WriteLine("Could not find certificate at '{0}'. HTTPS is not enabled.", testCertPath);
}
#endif
app.Run(async context =>
{
Console.WriteLine("{0} {1}{2}{3}",

View File

@ -17,9 +17,8 @@
}
},
"commands": {
"web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.Kestrel",
"run": "Microsoft.AspNet.Server.Kestrel",
"run-socket": "Microsoft.AspNet.Server.Kestrel --server.urls http://unix:/tmp/kestrel-test.sock",
"kestrel": "Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:5000;https://localhost:5001"
"web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:5000;https://localhost:5001",
"kestrel": "Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:5000;https://localhost:5001",
"run-socket": "Microsoft.AspNet.Server.Kestrel --server.urls http://unix:/tmp/kestrel-test.sock"
}
}

View File

@ -0,0 +1,178 @@
// 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.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.Server.Kestrel.Filter;
using Microsoft.AspNet.Server.Kestrel.Http;
using Xunit;
namespace Microsoft.AspNet.Server.KestrelTests
{
public class ConnectionFilterTests
{
private async Task App(Frame frame)
{
frame.ResponseHeaders.Clear();
while (true)
{
var buffer = new byte[8192];
var count = await frame.RequestBody.ReadAsync(buffer, 0, buffer.Length);
if (count == 0)
{
break;
}
await frame.ResponseBody.WriteAsync(buffer, 0, count);
}
}
[Fact]
public async Task CanReadAndWriteWithRewritingConnectionFilter()
{
var filter = new RewritingConnectionFilter();
var serviceContext = new TestServiceContext()
{
ConnectionFilter = filter
};
var sendString = "POST / HTTP/1.0\r\n\r\nHello World?";
using (var server = new TestServer(App, serviceContext))
{
using (var connection = new TestConnection())
{
// "?" changes to "!"
await connection.SendEnd(sendString);
await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
"",
"Hello World!");
}
}
Assert.Equal(sendString.Length, filter.BytesRead);
}
[Fact]
public async Task CanReadAndWriteWithAsyncConnectionFilter()
{
var serviceContext = new TestServiceContext()
{
ConnectionFilter = new AsyncConnectionFilter()
};
using (var server = new TestServer(App, serviceContext))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"POST / HTTP/1.0",
"",
"Hello World?");
await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
"",
"Hello World!");
}
}
}
private class RewritingConnectionFilter : IConnectionFilter
{
private static Task _empty = Task.FromResult<object>(null);
private RewritingStream _rewritingStream;
public Task OnConnection(ConnectionFilterContext context)
{
_rewritingStream = new RewritingStream(context.Connection);
context.Connection = _rewritingStream;
return _empty;
}
public int BytesRead => _rewritingStream.BytesRead;
}
private class AsyncConnectionFilter : IConnectionFilter
{
public async Task OnConnection(ConnectionFilterContext context)
{
var oldConnection = context.Connection;
// Set Connection to null to ensure it isn't used until the returned task completes.
context.Connection = null;
await Task.Delay(100);
context.Connection = new RewritingStream(oldConnection);
}
}
private class RewritingStream : Stream
{
private readonly Stream _innerStream;
public RewritingStream(Stream innerStream)
{
_innerStream = innerStream;
}
public int BytesRead { get; private set; }
public override bool CanRead => _innerStream.CanRead;
public override bool CanSeek => _innerStream.CanSeek;
public override bool CanWrite => _innerStream.CanWrite;
public override long Length => _innerStream.Length;
public override long Position
{
get
{
return _innerStream.Position;
}
set
{
_innerStream.Position = value;
}
}
public override void Flush()
{
// No-op
}
public override int Read(byte[] buffer, int offset, int count)
{
var actual = _innerStream.Read(buffer, offset, count);
BytesRead += actual;
return actual;
}
public override long Seek(long offset, SeekOrigin origin)
{
return _innerStream.Seek(offset, origin);
}
public override void SetLength(long value)
{
_innerStream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
for (int i = 0; i < buffer.Length; i++)
{
if (buffer[i] == '?')
{
buffer[i] = (byte)'!';
}
}
_innerStream.Write(buffer, offset, count);
}
}
}
}

View File

@ -8,6 +8,7 @@ using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Server.Kestrel;
using Microsoft.AspNet.Server.Kestrel.Filter;
using Microsoft.AspNet.Server.Kestrel.Http;
using Microsoft.Dnx.Runtime;
using Microsoft.Dnx.Runtime.Infrastructure;
@ -20,6 +21,25 @@ namespace Microsoft.AspNet.Server.KestrelTests
/// </summary>
public class EngineTests
{
public static TheoryData<ServiceContext> ConnectionFilterData
{
get
{
return new TheoryData<ServiceContext>
{
{
new TestServiceContext()
},
{
new TestServiceContext
{
ConnectionFilter = new NoOpConnectionFilter()
}
}
};
}
}
private async Task App(Frame frame)
{
frame.ResponseHeaders.Clear();
@ -75,18 +95,20 @@ namespace Microsoft.AspNet.Server.KestrelTests
return Task.FromResult<object>(null);
}
[Fact]
public void EngineCanStartAndStop()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public void EngineCanStartAndStop(ServiceContext testContext)
{
var engine = new KestrelEngine(LibraryManager, new TestServiceContext());
var engine = new KestrelEngine(LibraryManager, testContext);
engine.Start(1);
engine.Dispose();
}
[Fact]
public void ListenerCanCreateAndDispose()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public void ListenerCanCreateAndDispose(ServiceContext testContext)
{
var engine = new KestrelEngine(LibraryManager, new TestServiceContext());
var engine = new KestrelEngine(LibraryManager, testContext);
engine.Start(1);
var address = ServerAddress.FromUrl("http://localhost:54321/");
var started = engine.CreateServer(address, App);
@ -94,11 +116,11 @@ namespace Microsoft.AspNet.Server.KestrelTests
engine.Dispose();
}
[Fact]
public void ConnectionCanReadAndWrite()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public void ConnectionCanReadAndWrite(ServiceContext testContext)
{
var engine = new KestrelEngine(LibraryManager, new TestServiceContext());
var engine = new KestrelEngine(LibraryManager, testContext);
engine.Start(1);
var address = ServerAddress.FromUrl("http://localhost:54321/");
var started = engine.CreateServer(address, App);
@ -119,10 +141,12 @@ namespace Microsoft.AspNet.Server.KestrelTests
engine.Dispose();
}
[Fact]
public async Task Http10()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task Http10(ServiceContext testContext)
{
using (var server = new TestServer(App))
using (var server = new TestServer(App, testContext))
{
using (var connection = new TestConnection())
{
@ -138,10 +162,12 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task Http11()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task Http11(ServiceContext testContext)
{
using (var server = new TestServer(AppChunked))
using (var server = new TestServer(AppChunked, testContext))
{
using (var connection = new TestConnection())
{
@ -165,11 +191,11 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task Http10ContentLength()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task Http10ContentLength(ServiceContext testContext)
{
using (var server = new TestServer(App))
using (var server = new TestServer(App, testContext))
{
using (var connection = new TestConnection())
{
@ -186,10 +212,11 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task Http10TransferEncoding()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task Http10TransferEncoding(ServiceContext testContext)
{
using (var server = new TestServer(App))
using (var server = new TestServer(App, testContext))
{
using (var connection = new TestConnection())
{
@ -206,11 +233,11 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task Http10KeepAlive()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task Http10KeepAlive(ServiceContext testContext)
{
using (var server = new TestServer(AppChunked))
using (var server = new TestServer(AppChunked, testContext))
{
using (var connection = new TestConnection())
{
@ -235,10 +262,11 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task Http10KeepAliveNotUsedIfResponseContentLengthNotSet()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task Http10KeepAliveNotUsedIfResponseContentLengthNotSet(ServiceContext testContext)
{
using (var server = new TestServer(App))
using (var server = new TestServer(App, testContext))
{
using (var connection = new TestConnection())
{
@ -264,8 +292,9 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task Http10KeepAliveContentLength()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task Http10KeepAliveContentLength(ServiceContext testContext)
{
using (var server = new TestServer(AppChunked))
{
@ -294,8 +323,9 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task Http10KeepAliveTransferEncoding()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task Http10KeepAliveTransferEncoding(ServiceContext testContext)
{
using (var server = new TestServer(AppChunked))
{
@ -325,10 +355,11 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task Expect100ContinueForBody()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task Expect100ContinueForBody(ServiceContext testContext)
{
using (var server = new TestServer(AppChunked))
using (var server = new TestServer(AppChunked, testContext))
{
using (var connection = new TestConnection())
{
@ -350,11 +381,11 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task DisconnectingClient()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task DisconnectingClient(ServiceContext testContext)
{
using (var server = new TestServer(App))
using (var server = new TestServer(App, testContext))
{
var socket = new Socket(SocketType.Stream, ProtocolType.IP);
socket.Connect(IPAddress.Loopback, 54321);
@ -375,10 +406,11 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task ZeroContentLengthSetAutomaticallyAfterNoWrites()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task ZeroContentLengthSetAutomaticallyAfterNoWrites(ServiceContext testContext)
{
using (var server = new TestServer(EmptyApp))
using (var server = new TestServer(EmptyApp, testContext))
{
using (var connection = new TestConnection())
{
@ -402,10 +434,11 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task ZeroContentLengthNotSetAutomaticallyForNonKeepAliveRequests()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task ZeroContentLengthNotSetAutomaticallyForNonKeepAliveRequests(ServiceContext testContext)
{
using (var server = new TestServer(EmptyApp))
using (var server = new TestServer(EmptyApp, testContext))
{
using (var connection = new TestConnection())
{
@ -435,10 +468,11 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task ZeroContentLengthNotSetAutomaticallyForHeadRequests()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task ZeroContentLengthNotSetAutomaticallyForHeadRequests(ServiceContext testContext)
{
using (var server = new TestServer(EmptyApp))
using (var server = new TestServer(EmptyApp, testContext))
{
using (var connection = new TestConnection())
{
@ -454,8 +488,9 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes(ServiceContext testContext)
{
using (var server = new TestServer(async frame =>
{
@ -466,7 +501,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
var statusString = await reader.ReadLineAsync();
frame.StatusCode = int.Parse(statusString);
}
}))
}, testContext))
{
using (var connection = new TestConnection())
{
@ -504,8 +539,9 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task ThrowingResultsIn500Response()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task ThrowingResultsIn500Response(ServiceContext testContext)
{
bool onStartingCalled = false;
@ -521,7 +557,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
frame.ResponseHeaders.Clear();
frame.ResponseHeaders["Content-Length"] = new[] { "11" };
throw new Exception();
}))
}, testContext))
{
using (var connection = new TestConnection())
{
@ -555,8 +591,9 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task ThrowingAfterWritingKillsConnection()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task ThrowingAfterWritingKillsConnection(ServiceContext testContext)
{
bool onStartingCalled = false;
@ -572,7 +609,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
frame.ResponseHeaders["Content-Length"] = new[] { "11" };
await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
throw new Exception();
}))
}, testContext))
{
using (var connection = new TestConnection())
{
@ -591,8 +628,9 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task ThrowingAfterPartialWriteKillsConnection()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task ThrowingAfterPartialWriteKillsConnection(ServiceContext testContext)
{
bool onStartingCalled = false;
@ -608,7 +646,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
frame.ResponseHeaders["Content-Length"] = new[] { "11" };
await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Hello"), 0, 5);
throw new Exception();
}))
}, testContext))
{
using (var connection = new TestConnection())
{
@ -627,10 +665,11 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task ConnectionClosesWhenFinReceived()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task ConnectionClosesWhenFinReceived(ServiceContext testContext)
{
using (var server = new TestServer(AppChunked))
using (var server = new TestServer(AppChunked, testContext))
{
using (var connection = new TestConnection())
{
@ -653,10 +692,11 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task ConnectionClosesWhenFinReceivedBeforeRequestCompletes()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task ConnectionClosesWhenFinReceivedBeforeRequestCompletes(ServiceContext testContext)
{
using (var server = new TestServer(AppChunked))
using (var server = new TestServer(AppChunked, testContext))
{
using (var connection = new TestConnection())
{
@ -694,8 +734,9 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task ThrowingInOnStartingResultsIn500Response()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task ThrowingInOnStartingResultsIn500Response(ServiceContext testContext)
{
using (var server = new TestServer(frame =>
{
@ -710,7 +751,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
// If we write to the response stream, we will not get a 500.
return Task.FromResult<object>(null);
}))
}, testContext))
{
using (var connection = new TestConnection())
{
@ -742,8 +783,9 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
[Fact]
public async Task ThrowingInOnStartingResultsInFailedWrites()
[Theory]
[MemberData(nameof(ConnectionFilterData))]
public async Task ThrowingInOnStartingResultsInFailedWrites(ServiceContext testContext)
{
using (var server = new TestServer(async frame =>
{
@ -764,7 +806,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
// The second write should succeed since the OnStarting callback will not be called again
await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Exception!!"), 0, 11);
}))
}, testContext))
{
using (var connection = new TestConnection())
{

View File

@ -0,0 +1,74 @@
// 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 DNX451
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.AspNet.Server.Kestrel.Filter;
using Microsoft.AspNet.Server.Kestrel.Http;
using Microsoft.AspNet.Server.Kestrel.Https;
using Microsoft.AspNet.Testing.xunit;
using Xunit;
namespace Microsoft.AspNet.Server.KestrelTests
{
public class HttpsConnectionFilterTests
{
private async Task App(Frame frame)
{
frame.ResponseHeaders.Clear();
while (true)
{
var buffer = new byte[8192];
var count = await frame.RequestBody.ReadAsync(buffer, 0, buffer.Length);
if (count == 0)
{
break;
}
await frame.ResponseBody.WriteAsync(buffer, 0, count);
}
}
// https://github.com/aspnet/KestrelHttpServer/issues/240
// This test currently fails on mono because of an issue with SslStream.
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
public async Task CanReadAndWriteWithHttpsConnectionFilter()
{
RemoteCertificateValidationCallback validationCallback =
(sender, cert, chain, sslPolicyErrors) => true;
try
{
ServicePointManager.ServerCertificateValidationCallback += validationCallback;
var sereverAddress = "https://localhost:54321/";
var serviceContext = new TestServiceContext()
{
ConnectionFilter = new HttpsConnectionFilter(new X509Certificate2(@"TestResources/testCert.cer"), new NoOpConnectionFilter())
};
using (var server = new TestServer(App, serviceContext, sereverAddress))
{
using (var client = new HttpClient())
{
var result = await client.PostAsync(sereverAddress, new FormUrlEncodedContent(new[] {
new KeyValuePair<string, string>("content", "Hello World?")
}));
Assert.Equal("content=Hello+World%3F", await result.Content.ReadAsStringAsync());
}
}
}
finally
{
ServicePointManager.ServerCertificateValidationCallback -= validationCallback;
}
}
}
}
#endif

View File

@ -19,8 +19,17 @@ namespace Microsoft.AspNet.Server.KestrelTests
private IDisposable _server;
public TestServer(Func<Frame, Task> app)
: this(app, new TestServiceContext())
{
Create(app);
}
public TestServer(Func<Frame, Task> app, ServiceContext context)
: this(app, context, "http://localhost:54321/")
{
}
public TestServer(Func<Frame, Task> app, ServiceContext context, string serverAddress)
{
Create(app, context, serverAddress);
}
ILibraryManager LibraryManager
@ -45,14 +54,14 @@ namespace Microsoft.AspNet.Server.KestrelTests
}
}
public void Create(Func<Frame, Task> app)
public void Create(Func<Frame, Task> app, ServiceContext context, string serverAddress)
{
_engine = new KestrelEngine(
LibraryManager,
new TestServiceContext());
context);
_engine.Start(1);
_server = _engine.CreateServer(
ServerAddress.FromUrl("http://localhost:54321/"),
ServerAddress.FromUrl(serverAddress),
app);
}

View File

@ -2,10 +2,16 @@
"version": "1.0.0-*",
"dependencies": {
"xunit.runner.aspnet": "2.0.0-aspnet-*",
"Microsoft.AspNet.Server.Kestrel": "1.0.0-*"
"Microsoft.AspNet.Server.Kestrel": "1.0.0-*",
"Microsoft.AspNet.Testing": "1.0.0-*"
},
"frameworks": {
"dnx451": { },
"dnx451": {
"dependencies": {
"System.Net.Http": "4.0.1-beta-*",
"Microsoft.AspNet.Server.Kestrel.Https": "1.0.0-*"
}
},
"dnxcore50": {
"dependencies": {
"System.Diagnostics.TraceSource": "4.0.0-beta-*",