Adding some tests for Http11

Requires updating to test utilities
Adding initial EventSource to try to see some order of events
This commit is contained in:
Louis DeJardin 2014-06-11 17:09:25 -07:00
parent 660babcd7f
commit 9c7cb6a958
10 changed files with 313 additions and 198 deletions

View File

@ -54,6 +54,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
private readonly UvStreamHandle _socket; private readonly UvStreamHandle _socket;
private Frame _frame; private Frame _frame;
long _connectionId;
public Connection(ListenerContext context, UvStreamHandle socket) : base(context) public Connection(ListenerContext context, UvStreamHandle socket) : base(context)
{ {
@ -63,6 +64,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
public void Start() public void Start()
{ {
KestrelTrace.Log.ConnectionStart(_connectionId);
SocketInput = new SocketInput(Memory); SocketInput = new SocketInput(Memory);
SocketOutput = new SocketOutput(Thread, _socket); SocketOutput = new SocketOutput(Thread, _socket);
_frame = new Frame(this); _frame = new Frame(this);
@ -85,6 +88,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
if (nread == 0) if (nread == 0)
{ {
SocketInput.RemoteIntakeFin = true; SocketInput.RemoteIntakeFin = true;
KestrelTrace.Log.ConnectionReadFin(_connectionId);
}
else
{
KestrelTrace.Log.ConnectionRead(_connectionId, nread);
} }
_frame.Consume(); _frame.Consume();
@ -92,11 +100,13 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
void IConnectionControl.Pause() void IConnectionControl.Pause()
{ {
KestrelTrace.Log.ConnectionPause(_connectionId);
_socket.ReadStop(); _socket.ReadStop();
} }
void IConnectionControl.Resume() void IConnectionControl.Resume()
{ {
KestrelTrace.Log.ConnectionResume(_connectionId);
_socket.ReadStart(_allocCallback, _readCallback, this); _socket.ReadStart(_allocCallback, _readCallback, this);
} }
@ -105,25 +115,37 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
switch (endType) switch (endType)
{ {
case ProduceEndType.SocketShutdownSend: case ProduceEndType.SocketShutdownSend:
KestrelTrace.Log.ConnectionWriteFin(_connectionId, 0);
Thread.Post( Thread.Post(
x => x =>
{ {
KestrelTrace.Log.ConnectionWriteFin(_connectionId, 1);
var self = (Connection)x; var self = (Connection)x;
var shutdown = new UvShutdownReq(); var shutdown = new UvShutdownReq();
shutdown.Init(self.Thread.Loop); shutdown.Init(self.Thread.Loop);
shutdown.Shutdown(self._socket, (req, status, state) => req.Dispose(), null); shutdown.Shutdown(self._socket, (req, status, state) =>
{
KestrelTrace.Log.ConnectionWriteFin(_connectionId, 1);
req.Dispose();
}, null);
}, },
this); this);
break; break;
case ProduceEndType.ConnectionKeepAlive: case ProduceEndType.ConnectionKeepAlive:
KestrelTrace.Log.ConnectionKeepAlive(_connectionId);
_frame = new Frame(this); _frame = new Frame(this);
Thread.Post( Thread.Post(
x => ((Frame)x).Consume(), x => ((Frame)x).Consume(),
_frame); _frame);
break; break;
case ProduceEndType.SocketDisconnect: case ProduceEndType.SocketDisconnect:
KestrelTrace.Log.ConnectionDisconnect(_connectionId);
Thread.Post( Thread.Post(
x => ((UvHandle)x).Dispose(), x =>
{
KestrelTrace.Log.ConnectionStop(_connectionId);
((UvHandle)x).Dispose();
},
_socket); _socket);
break; break;
} }

View File

@ -321,8 +321,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
ConnectionControl.End(ProduceEndType.SocketShutdownSend); ConnectionControl.End(ProduceEndType.SocketShutdownSend);
} }
_messageBody.Drain(() => //NOTE: must finish reading request body
ConnectionControl.End(_keepAlive ? ProduceEndType.ConnectionKeepAlive : ProduceEndType.SocketDisconnect)); ConnectionControl.End(_keepAlive ? ProduceEndType.ConnectionKeepAlive : ProduceEndType.SocketDisconnect);
} }

View File

@ -3,13 +3,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
namespace Microsoft.AspNet.Server.Kestrel.Http namespace Microsoft.AspNet.Server.Kestrel.Http
{ {
public abstract class MessageBody : MessageBodyExchanger public abstract class MessageBody : MessageBodyExchanger
{ {
private Action _continuation = () => { };
public bool RequestKeepAlive { get; protected set; } public bool RequestKeepAlive { get; protected set; }
protected MessageBody(FrameContext context) : base(context) protected MessageBody(FrameContext context) : base(context)
@ -24,10 +23,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
public void IntakeFin(int count) public void IntakeFin(int count)
{ {
Transfer(count, true); Transfer(count, true);
if (_continuation != null)
{
_continuation.Invoke();
}
} }
public abstract void Consume(); public abstract void Consume();
@ -90,12 +85,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
return true; return true;
} }
public void Drain(Action continuation)
{
_continuation = continuation;
_continuation.Invoke();
}
class ForRemainingData : MessageBody class ForRemainingData : MessageBody
{ {

View File

@ -28,6 +28,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
public void Write(ArraySegment<byte> buffer, Action<object> callback, object state) public void Write(ArraySegment<byte> buffer, Action<object> callback, object state)
{ {
KestrelTrace.Log.ConnectionWrite(0, buffer.Count);
var req = new ThisWriteReq(); var req = new ThisWriteReq();
req.Init(_thread.Loop); req.Init(_thread.Loop);
req.Contextualize(this, _socket, buffer, callback, state); req.Contextualize(this, _socket, buffer, callback, state);
@ -87,6 +88,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
private void OnWrite(UvWriteReq req, int status) private void OnWrite(UvWriteReq req, int status)
{ {
_pin.Free(); _pin.Free();
KestrelTrace.Log.ConnectionWriteCallback(0, status);
//NOTE: pool this? //NOTE: pool this?
Dispose(); Dispose();
_callback(_state); _callback(_state);

View File

@ -0,0 +1,83 @@
using System;
using System.Diagnostics.Tracing;
namespace Microsoft.AspNet.Server.Kestrel
{
/// <summary>
/// Summary description for KestrelTrace
/// </summary>
public class KestrelTrace : EventSource
{
public static KestrelTrace Log = new KestrelTrace();
static EventTask Connection = (EventTask)1;
static EventTask Frame = (EventTask)1;
[Event(13, Level = EventLevel.Informational, Message = "Id {0}")]
public void ConnectionStart(long connectionId)
{
WriteEvent(13, connectionId);
}
[Event(14, Level = EventLevel.Informational, Message = "Id {0}")]
public void ConnectionStop(long connectionId)
{
WriteEvent(14, connectionId);
}
[Event(4, Message = "Id {0} Status {1}")]
internal void ConnectionRead(long connectionId, int status)
{
WriteEvent(4, connectionId, status);
}
[Event(5, Message = "Id {0}")]
internal void ConnectionPause(long connectionId)
{
WriteEvent(5, connectionId);
}
[Event(6, Message = "Id {0}")]
internal void ConnectionResume(long connectionId)
{
WriteEvent(6, connectionId);
}
[Event(7, Message = "Id {0}")]
internal void ConnectionReadFin(long connectionId)
{
WriteEvent(7, connectionId);
}
[Event(8, Message = "Id {0} Step {1}")]
internal void ConnectionWriteFin(long connectionId, int step)
{
WriteEvent(8, connectionId, step);
}
[Event(9, Message = "Id {0}")]
internal void ConnectionKeepAlive(long connectionId)
{
WriteEvent(9, connectionId);
}
[Event(10, Message = "Id {0}")]
internal void ConnectionDisconnect(long connectionId)
{
WriteEvent(10, connectionId);
}
[Event(11, Message = "Id {0} Count {1}")]
internal void ConnectionWrite(long connectionId, int count)
{
WriteEvent(11, connectionId, count);
}
[Event(12, Message = "Id {0} Status {1}")]
internal void ConnectionWriteCallback(long connectionId, int status)
{
WriteEvent(12, connectionId, status);
}
}
}

View File

@ -42,6 +42,7 @@
<Compile Include="Http\SocketOutput.cs" /> <Compile Include="Http\SocketOutput.cs" />
<Compile Include="Infrastructure\Disposable.cs" /> <Compile Include="Infrastructure\Disposable.cs" />
<Compile Include="Infrastructure\KestrelThread.cs" /> <Compile Include="Infrastructure\KestrelThread.cs" />
<Compile Include="Infrastructure\KestrelTrace.cs" />
<Compile Include="Networking\UcAsyncHandle.cs" /> <Compile Include="Networking\UcAsyncHandle.cs" />
<Compile Include="Networking\UvMemory.cs" /> <Compile Include="Networking\UvMemory.cs" />
<Compile Include="Networking\UvShutdownReq.cs" /> <Compile Include="Networking\UvShutdownReq.cs" />
@ -54,4 +55,4 @@
<Compile Include="Networking\UvWriteRequest.cs" /> <Compile Include="Networking\UvWriteRequest.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project> </Project>

View File

@ -12,7 +12,8 @@
"System.Threading.Thread": "4.0.0.0", "System.Threading.Thread": "4.0.0.0",
"System.Diagnostics.TraceSource": "4.0.0.0", "System.Diagnostics.TraceSource": "4.0.0.0",
"System.Text.Encoding": "4.0.20.0", "System.Text.Encoding": "4.0.20.0",
"System.Threading.Tasks": "4.0.10.0" "System.Threading.Tasks": "4.0.10.0",
"System.Diagnostics.Tracing": "4.0.10.0"
} }
} }
}, },

View File

@ -95,226 +95,204 @@ namespace Microsoft.AspNet.Server.KestralTests
[Fact] [Fact]
public async Task Http10() public async Task Http10()
{ {
var engine = new KestrelEngine(); using (var server = new TestServer(App))
engine.Start(1); {
var started = engine.CreateServer(App); using (var connection = new TestConnection())
{
Transceive( await connection.SendEnd(
@"POST / HTTP/1.0 "POST / HTTP/1.0",
"",
Hello World", "Hello World");
@"HTTP/1.0 200 OK await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
Hello World"); "",
started.Dispose(); "Hello World");
engine.Stop(); }
}
} }
[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] [Fact]
public async Task Http10ContentLength() public async Task Http10ContentLength()
{ {
var engine = new KestrelEngine(); using (var server = new TestServer(App))
engine.Start(1); {
var started = engine.CreateServer(App); using (var connection = new TestConnection())
{
Transceive( await connection.Send(
@"POST / HTTP/1.0 "POST / HTTP/1.0",
Content-Length: 11 "Content-Length: 11",
"",
Hello World", "Hello World");
@"HTTP/1.0 200 OK await connection.ReceiveEnd(
"HTTP/1.0 200 OK",
Hello World"); "",
started.Dispose(); "Hello World");
engine.Stop(); }
}
} }
[Fact] [Fact]
public async Task Http10TransferEncoding() public async Task Http10TransferEncoding()
{ {
var engine = new KestrelEngine(); using (var server = new TestServer(App))
engine.Start(1);
var started = engine.CreateServer(App);
using (var connection = new TestConnection())
{ {
await connection.Send( using (var connection = new TestConnection())
"POST / HTTP/1.0", {
"Transfer-Encoding: chunked", await connection.Send(
"", "POST / HTTP/1.0",
"5", "Hello", "6", " World", "0\r\n"); "Transfer-Encoding: chunked",
await connection.ReceiveEnd( "",
"HTTP/1.0 200 OK", "5", "Hello", "6", " World", "0\r\n");
"", await connection.ReceiveEnd(
"Hello World"); "HTTP/1.0 200 OK",
"",
"Hello World");
}
} }
started.Dispose();
engine.Stop();
} }
[Fact] [Fact]
public async Task Http10KeepAlive() public async Task Http10KeepAlive()
{ {
var engine = new KestrelEngine(); using (var server = new TestServer(AppChunked))
engine.Start(1);
var started = engine.CreateServer(AppChunked);
using (var connection = new TestConnection())
{ {
await connection.SendEnd( using (var connection = new TestConnection())
"GET / HTTP/1.0", {
"Connection: keep-alive", await connection.SendEnd(
"", "GET / HTTP/1.0",
"POST / HTTP/1.0", "Connection: keep-alive",
"", "",
"Goodbye"); "POST / HTTP/1.0",
await connection.Receive( "",
"HTTP/1.0 200 OK", "Goodbye");
"Content-Length: 0", await connection.Receive(
"Connection: keep-alive", "HTTP/1.0 200 OK",
"\r\n"); "Content-Length: 0",
await connection.ReceiveEnd( "Connection: keep-alive",
"HTTP/1.0 200 OK", "\r\n");
"Content-Length: 7", await connection.ReceiveEnd(
"", "HTTP/1.0 200 OK",
"Goodbye"); "Content-Length: 7",
"",
"Goodbye");
}
} }
started.Dispose();
engine.Stop();
} }
[Fact] [Fact]
public async Task Http10KeepAliveContentLength() public async Task Http10KeepAliveContentLength()
{ {
var engine = new KestrelEngine(); using (var server = new TestServer(AppChunked))
engine.Start(1);
var started = engine.CreateServer(AppChunked);
using (var connection = new TestConnection())
{ {
await connection.SendEnd( using (var connection = new TestConnection())
"POST / HTTP/1.0", {
"Connection: keep-alive", await connection.SendEnd(
"Content-Length: 11", "POST / HTTP/1.0",
"", "Connection: keep-alive",
"Hello WorldPOST / HTTP/1.0", "Content-Length: 11",
"", "",
"Goodbye"); "Hello WorldPOST / HTTP/1.0",
await connection.Receive( "",
"HTTP/1.0 200 OK", "Goodbye");
"Content-Length: 11", await connection.Receive(
"Connection: keep-alive", "HTTP/1.0 200 OK",
"", "Content-Length: 11",
"Hello World"); "Connection: keep-alive",
await connection.ReceiveEnd( "",
"HTTP/1.0 200 OK", "Hello World");
"Content-Length: 7", await connection.ReceiveEnd(
"", "HTTP/1.0 200 OK",
"Goodbye"); "Content-Length: 7",
"",
"Goodbye");
}
} }
started.Dispose();
engine.Stop();
} }
[Fact] [Fact]
public async Task Http10KeepAliveTransferEncoding() public async Task Http10KeepAliveTransferEncoding()
{ {
var engine = new KestrelEngine(); using (var server = new TestServer(AppChunked))
engine.Start(1);
var started = engine.CreateServer(AppChunked);
using (var connection = new TestConnection())
{ {
await connection.SendEnd( using (var connection = new TestConnection())
"POST / HTTP/1.0", {
"Transfer-Encoding: chunked", await connection.SendEnd(
"Connection: keep-alive", "POST / HTTP/1.0",
"", "Transfer-Encoding: chunked",
"5", "Hello", "6", " World", "0", "Connection: keep-alive",
"POST / HTTP/1.0", "",
"", "5", "Hello", "6", " World", "0",
"Goodbye"); "POST / HTTP/1.0",
await connection.Receive( "",
"HTTP/1.0 200 OK", "Goodbye");
"Content-Length: 11", await connection.Receive(
"Connection: keep-alive", "HTTP/1.0 200 OK",
"", "Content-Length: 11",
"Hello World"); "Connection: keep-alive",
await connection.ReceiveEnd( "",
"HTTP/1.0 200 OK", "Hello World");
"Content-Length: 7", await connection.ReceiveEnd(
"", "HTTP/1.0 200 OK",
"Goodbye"); "Content-Length: 7",
"",
"Goodbye");
}
} }
started.Dispose();
engine.Stop();
} }
[Fact] [Fact]
public async Task Expect100ContinueForBody() public async Task Expect100ContinueForBody()
{ {
var engine = new KestrelEngine(); using (var server = new TestServer(AppChunked))
engine.Start(1);
var started = engine.CreateServer(AppChunked);
using (var connection = new TestConnection())
{ {
await connection.Send( using (var connection = new TestConnection())
"POST / HTTP/1.1", {
"Expect: 100-continue", await connection.Send(
"Content-Length: 11", "POST / HTTP/1.1",
"Connection: close", "Expect: 100-continue",
"\r\n"); "Content-Length: 11",
await connection.Receive("HTTP/1.1 100 Continue", "\r\n"); "Connection: close",
await connection.SendEnd("Hello World"); "\r\n");
await connection.Receive( await connection.Receive("HTTP/1.1 100 Continue", "\r\n");
"HTTP/1.1 200 OK", await connection.SendEnd("Hello World");
"Content-Length: 11", await connection.Receive(
"Connection: close", "HTTP/1.1 200 OK",
"", "Content-Length: 11",
"Hello World"); "Connection: close",
"",
"Hello World");
}
} }
started.Dispose();
engine.Stop();
} }
private void Transceive(string send, string expected)
{
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(new IPEndPoint(IPAddress.Loopback, 4001));
var stream = new NetworkStream(socket, false);
Task.Run(async () =>
{
try
{
var writer = new StreamWriter(stream, Encoding.ASCII);
foreach (var ch in send)
{
await writer.WriteAsync(ch);
await writer.FlushAsync();
await Task.Delay(TimeSpan.FromMilliseconds(5));
}
writer.Flush();
stream.Flush();
socket.Shutdown(SocketShutdown.Send);
}
catch (Exception ex)
{
}
});
var reader = new StreamReader(stream, Encoding.ASCII);
var actual = reader.ReadToEnd();
Assert.Equal(expected, actual);
}
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
@ -40,8 +41,9 @@ namespace Microsoft.AspNet.Server.KestralTests
{ {
var text = String.Join("\r\n", lines); var text = String.Join("\r\n", lines);
var writer = new StreamWriter(_stream, Encoding.ASCII); var writer = new StreamWriter(_stream, Encoding.ASCII);
foreach (var ch in text) for (var index = 0; index != text.Length; ++index)
{ {
var ch = text[index];
await writer.WriteAsync(ch); await writer.WriteAsync(ch);
await writer.FlushAsync(); await writer.FlushAsync();
await Task.Delay(TimeSpan.FromMilliseconds(5)); await Task.Delay(TimeSpan.FromMilliseconds(5));
@ -64,7 +66,10 @@ namespace Microsoft.AspNet.Server.KestralTests
while (offset < expected.Length) while (offset < expected.Length)
{ {
var task = _reader.ReadAsync(actual, offset, actual.Length - offset); var task = _reader.ReadAsync(actual, offset, actual.Length - offset);
// Assert.True(task.Wait(1000), "timeout"); if (!Debugger.IsAttached)
{
Assert.True(task.Wait(1000), "timeout");
}
var count = await task; var count = await task;
if (count == 0) if (count == 0)
{ {

View File

@ -0,0 +1,34 @@
using Microsoft.AspNet.Server.Kestrel;
using System;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Server.KestralTests
{
/// <summary>
/// Summary description for TestServer
/// </summary>
public class TestServer : IDisposable
{
private KestrelEngine _engine;
private IDisposable _server;
public TestServer(Func<object, Task> app)
{
Create(app);
}
public void Create(Func<object,Task> app)
{
_engine = new KestrelEngine();
_engine.Start(1);
_server = _engine.CreateServer(app);
}
public void Dispose()
{
_server.Dispose();
_engine.Stop();
}
}
}