aspnetcore/test/Microsoft.AspNet.Server.Kes.../EngineTests.cs

792 lines
26 KiB
C#

// 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.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Server.Kestrel;
using Microsoft.AspNet.Server.Kestrel.Http;
using Microsoft.Dnx.Runtime;
using Microsoft.Dnx.Runtime.Infrastructure;
using Xunit;
namespace Microsoft.AspNet.Server.KestrelTests
{
/// <summary>
/// Summary description for EngineTests
/// </summary>
public class EngineTests
{
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);
}
}
ILibraryManager LibraryManager
{
get
{
try
{
var locator = CallContextServiceLocator.Locator;
if (locator == null)
{
return null;
}
var services = locator.ServiceProvider;
if (services == null)
{
return null;
}
return (ILibraryManager)services.GetService(typeof(ILibraryManager));
}
catch (NullReferenceException)
{ return null; }
}
}
private async Task AppChunked(Frame frame)
{
Console.WriteLine($"----");
Console.WriteLine($"{frame.Method} {frame.RequestUri} {frame.HttpVersion}");
foreach (var h in frame.RequestHeaders)
{
Console.WriteLine($"{h.Key}: {h.Value}");
}
Console.WriteLine($"");
var data = new MemoryStream();
await frame.RequestBody.CopyToAsync(data);
var bytes = data.ToArray();
Console.WriteLine($"{Encoding.ASCII.GetString(bytes)}");
frame.ResponseHeaders.Clear();
frame.ResponseHeaders["Content-Length"] = new[] { bytes.Length.ToString() };
await frame.ResponseBody.WriteAsync(bytes, 0, bytes.Length);
}
private Task EmptyApp(Frame frame)
{
frame.ResponseHeaders.Clear();
return Task.FromResult<object>(null);
}
[Fact]
public void EngineCanStartAndStop()
{
var engine = new KestrelEngine(LibraryManager, new TestServiceContext());
engine.Start(1);
engine.Dispose();
}
[Fact]
public void ListenerCanCreateAndDispose()
{
var engine = new KestrelEngine(LibraryManager, new TestServiceContext());
engine.Start(1);
var started = engine.CreateServer("http", "localhost", 54321, App);
started.Dispose();
engine.Dispose();
}
[Fact]
public void ConnectionCanReadAndWrite()
{
var engine = new KestrelEngine(LibraryManager, new TestServiceContext());
engine.Start(1);
var started = engine.CreateServer("http", "localhost", 54321, App);
Console.WriteLine("Started");
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(new IPEndPoint(IPAddress.Loopback, 54321));
socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.0\r\n\r\nHello World"));
socket.Shutdown(SocketShutdown.Send);
var buffer = new byte[8192];
for (;;)
{
var length = socket.Receive(buffer);
if (length == 0) { break; }
var text = Encoding.ASCII.GetString(buffer, 0, length);
}
started.Dispose();
engine.Dispose();
}
[Fact]
public async Task Http10()
{
using (var server = new TestServer(App))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"POST / HTTP/1.0",
"",
"Hello World");
await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
"",
"Hello World");
}
}
}
[Fact]
public async Task Http11()
{
using (var server = new TestServer(AppChunked))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"GET / HTTP/1.1",
"",
"GET / HTTP/1.1",
"Connection: close",
"",
"Goodbye");
await connection.ReceiveEnd(
"HTTP/1.1 200 OK",
"Content-Length: 0",
"",
"HTTP/1.1 200 OK",
"Content-Length: 7",
"Connection: close",
"",
"Goodbye");
}
}
}
[Fact]
public async Task Http10ContentLength()
{
using (var server = new TestServer(App))
{
using (var connection = new TestConnection())
{
await connection.Send(
"POST / HTTP/1.0",
"Content-Length: 11",
"",
"Hello World");
await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
"",
"Hello World");
}
}
}
[Fact]
public async Task Http10TransferEncoding()
{
using (var server = new TestServer(App))
{
using (var connection = new TestConnection())
{
await connection.Send(
"POST / HTTP/1.0",
"Transfer-Encoding: chunked",
"",
"5", "Hello", "6", " World", "0\r\n");
await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
"",
"Hello World");
}
}
}
[Fact]
public async Task Http10KeepAlive()
{
using (var server = new TestServer(AppChunked))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"GET / HTTP/1.0",
"Connection: keep-alive",
"",
"POST / HTTP/1.0",
"",
"Goodbye");
await connection.Receive(
"HTTP/1.0 200 OK",
"Content-Length: 0",
"Connection: keep-alive",
"\r\n");
await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
"Content-Length: 7",
"",
"Goodbye");
}
}
}
[Fact]
public async Task Http10KeepAliveNotUsedIfResponseContentLengthNotSet()
{
using (var server = new TestServer(App))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"GET / HTTP/1.0",
"Connection: keep-alive",
"",
"POST / HTTP/1.0",
"Connection: keep-alive",
"Content-Length: 7",
"",
"Goodbye");
await connection.Receive(
"HTTP/1.0 200 OK",
"Content-Length: 0",
"Connection: keep-alive",
"\r\n");
await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
"",
"Goodbye");
}
}
}
[Fact]
public async Task Http10KeepAliveContentLength()
{
using (var server = new TestServer(AppChunked))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"POST / HTTP/1.0",
"Connection: keep-alive",
"Content-Length: 11",
"",
"Hello WorldPOST / HTTP/1.0",
"",
"Goodbye");
await connection.Receive(
"HTTP/1.0 200 OK",
"Content-Length: 11",
"Connection: keep-alive",
"",
"Hello World");
await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
"Content-Length: 7",
"",
"Goodbye");
}
}
}
[Fact]
public async Task Http10KeepAliveTransferEncoding()
{
using (var server = new TestServer(AppChunked))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"POST / HTTP/1.0",
"Transfer-Encoding: chunked",
"Connection: keep-alive",
"",
"5", "Hello", "6", " World", "0",
"POST / HTTP/1.0",
"",
"Goodbye");
await connection.Receive(
"HTTP/1.0 200 OK",
"Content-Length: 11",
"Connection: keep-alive",
"",
"Hello World");
await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
"Content-Length: 7",
"",
"Goodbye");
}
}
}
[Fact]
public async Task Expect100ContinueForBody()
{
using (var server = new TestServer(AppChunked))
{
using (var connection = new TestConnection())
{
await connection.Send(
"POST / HTTP/1.1",
"Expect: 100-continue",
"Content-Length: 11",
"Connection: close",
"\r\n");
await connection.Receive("HTTP/1.1 100 Continue", "\r\n");
await connection.SendEnd("Hello World");
await connection.Receive(
"HTTP/1.1 200 OK",
"Content-Length: 11",
"Connection: close",
"",
"Hello World");
}
}
}
[Fact]
public async Task DisconnectingClient()
{
using (var server = new TestServer(App))
{
var socket = new Socket(SocketType.Stream, ProtocolType.IP);
socket.Connect(IPAddress.Loopback, 54321);
await Task.Delay(200);
socket.Disconnect(false);
socket.Dispose();
await Task.Delay(200);
using (var connection = new TestConnection())
{
await connection.SendEnd(
"GET / HTTP/1.0",
"\r\n");
await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
"\r\n");
}
}
}
[Fact]
public async Task ZeroContentLengthSetAutomaticallyAfterNoWrites()
{
using (var server = new TestServer(EmptyApp))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"GET / HTTP/1.1",
"",
"GET / HTTP/1.0",
"Connection: keep-alive",
"",
"");
await connection.ReceiveEnd(
"HTTP/1.1 200 OK",
"Content-Length: 0",
"",
"HTTP/1.0 200 OK",
"Content-Length: 0",
"Connection: keep-alive",
"",
"");
}
}
}
[Fact]
public async Task ZeroContentLengthNotSetAutomaticallyForNonKeepAliveRequests()
{
using (var server = new TestServer(EmptyApp))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"GET / HTTP/1.1",
"Connection: close",
"",
"");
await connection.ReceiveEnd(
"HTTP/1.1 200 OK",
"Connection: close",
"",
"");
}
using (var connection = new TestConnection())
{
await connection.SendEnd(
"GET / HTTP/1.0",
"",
"");
await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
"",
"");
}
}
}
[Fact]
public async Task ZeroContentLengthNotSetAutomaticallyForHeadRequests()
{
using (var server = new TestServer(EmptyApp))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"HEAD / HTTP/1.1",
"",
"");
await connection.ReceiveEnd(
"HTTP/1.1 200 OK",
"",
"");
}
}
}
[Fact]
public async Task ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes()
{
using (var server = new TestServer(async frame =>
{
frame.ResponseHeaders.Clear();
using (var reader = new StreamReader(frame.RequestBody, Encoding.ASCII))
{
var statusString = await reader.ReadLineAsync();
frame.StatusCode = int.Parse(statusString);
}
}))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"POST / HTTP/1.1",
"Content-Length: 3",
"",
"101POST / HTTP/1.1",
"Content-Length: 3",
"",
"204POST / HTTP/1.1",
"Content-Length: 3",
"",
"205POST / HTTP/1.1",
"Content-Length: 3",
"",
"304POST / HTTP/1.1",
"Content-Length: 3",
"",
"200");
await connection.ReceiveEnd(
"HTTP/1.1 101 Switching Protocols",
"",
"HTTP/1.1 204 No Content",
"",
"HTTP/1.1 205 Reset Content",
"",
"HTTP/1.1 304 Not Modified",
"",
"HTTP/1.1 200 OK",
"Content-Length: 0",
"",
"");
}
}
}
[Fact]
public async Task ThrowingResultsIn500Response()
{
bool onStartingCalled = false;
using (var server = new TestServer(frame =>
{
frame.OnStarting(_ =>
{
onStartingCalled = true;
return Task.FromResult<object>(null);
}, null);
// Anything added to the ResponseHeaders dictionary is ignored
frame.ResponseHeaders.Clear();
frame.ResponseHeaders["Content-Length"] = new[] { "11" };
throw new Exception();
}))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"GET / HTTP/1.1",
"",
"GET / HTTP/1.1",
"Connection: close",
"",
"");
await connection.Receive(
"HTTP/1.1 500 Internal Server Error",
"");
await connection.ReceiveStartsWith("Date:");
await connection.Receive(
"Content-Length: 0",
"Server: Kestrel",
"",
"HTTP/1.1 500 Internal Server Error",
"");
await connection.ReceiveStartsWith("Date:");
await connection.ReceiveEnd(
"Content-Length: 0",
"Server: Kestrel",
"Connection: close",
"",
"");
Assert.False(onStartingCalled);
}
}
}
[Fact]
public async Task ThrowingAfterWritingKillsConnection()
{
bool onStartingCalled = false;
using (var server = new TestServer(async frame =>
{
frame.OnStarting(_ =>
{
onStartingCalled = true;
return Task.FromResult<object>(null);
}, null);
frame.ResponseHeaders.Clear();
frame.ResponseHeaders["Content-Length"] = new[] { "11" };
await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
throw new Exception();
}))
{
using (var connection = new TestConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"",
"");
await connection.ReceiveEnd(
"HTTP/1.1 200 OK",
"Content-Length: 11",
"",
"Hello World");
Assert.True(onStartingCalled);
}
}
}
[Fact]
public async Task ThrowingAfterPartialWriteKillsConnection()
{
bool onStartingCalled = false;
using (var server = new TestServer(async frame =>
{
frame.OnStarting(_ =>
{
onStartingCalled = true;
return Task.FromResult<object>(null);
}, null);
frame.ResponseHeaders.Clear();
frame.ResponseHeaders["Content-Length"] = new[] { "11" };
await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Hello"), 0, 5);
throw new Exception();
}))
{
using (var connection = new TestConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"",
"");
await connection.ReceiveEnd(
"HTTP/1.1 200 OK",
"Content-Length: 11",
"",
"Hello");
Assert.True(onStartingCalled);
}
}
}
[Fact]
public async Task ConnectionClosesWhenFinReceived()
{
using (var server = new TestServer(AppChunked))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"GET / HTTP/1.1",
"",
"Post / HTTP/1.1",
"Content-Length: 7",
"",
"Goodbye");
await connection.ReceiveEnd(
"HTTP/1.1 200 OK",
"Content-Length: 0",
"",
"HTTP/1.1 200 OK",
"Content-Length: 7",
"",
"Goodbye");
}
}
}
[Fact]
public async Task ConnectionClosesWhenFinReceivedBeforeRequestCompletes()
{
using (var server = new TestServer(AppChunked))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"GET /");
await connection.ReceiveEnd();
}
using (var connection = new TestConnection())
{
await connection.SendEnd(
"GET / HTTP/1.1",
"",
"Post / HTTP/1.1");
await connection.ReceiveEnd(
"HTTP/1.1 200 OK",
"Content-Length: 0",
"",
"");
}
using (var connection = new TestConnection())
{
await connection.SendEnd(
"GET / HTTP/1.1",
"",
"Post / HTTP/1.1",
"Content-Length: 7");
await connection.ReceiveEnd(
"HTTP/1.1 200 OK",
"Content-Length: 0",
"",
"");
}
}
}
[Fact]
public async Task ThrowingInOnStartingResultsIn500Response()
{
using (var server = new TestServer(frame =>
{
frame.OnStarting(_ =>
{
throw new Exception();
}, null);
frame.ResponseHeaders.Clear();
frame.ResponseHeaders["Content-Length"] = new[] { "11" };
// If we write to the response stream, we will not get a 500.
return Task.FromResult<object>(null);
}))
{
using (var connection = new TestConnection())
{
await connection.SendEnd(
"GET / HTTP/1.1",
"",
"GET / HTTP/1.1",
"Connection: close",
"",
"");
await connection.Receive(
"HTTP/1.1 500 Internal Server Error",
"");
await connection.ReceiveStartsWith("Date:");
await connection.Receive(
"Content-Length: 0",
"Server: Kestrel",
"",
"HTTP/1.1 500 Internal Server Error",
"");
await connection.ReceiveStartsWith("Date:");
await connection.ReceiveEnd(
"Content-Length: 0",
"Server: Kestrel",
"Connection: close",
"",
"");
}
}
}
[Fact]
public async Task ThrowingInOnStartingResultsInFailedWrites()
{
using (var server = new TestServer(async frame =>
{
var onStartingException = new Exception();
frame.OnStarting(_ =>
{
throw onStartingException;
}, null);
frame.ResponseHeaders.Clear();
frame.ResponseHeaders["Content-Length"] = new[] { "11" };
var writeException = await Assert.ThrowsAsync<Exception>(async () =>
await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11));
Assert.Same(onStartingException, writeException);
// The second write should succeed since the OnStarting callback will not be called again
await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Exception!!"), 0, 11);
}))
{
using (var connection = new TestConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"",
"");
await connection.Receive(
"HTTP/1.1 200 OK",
"Content-Length: 11",
"",
"Exception!!"); ;
}
}
}
}
}