From 044bbb83e6cfa126711677c7e810ad4c1da0e10e Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Sat, 7 Jun 2014 02:49:25 -0700 Subject: [PATCH] Getting request body variations to work --- .../Http/Connection.cs | 65 ++---- .../Http/Frame.cs | 19 +- .../Http/MessageBody.cs | 20 -- .../Networking/Libuv.cs | 21 ++ .../Networking/UvHandle.cs | 2 - .../Networking/UvMemory.cs | 17 +- .../Networking/UvShutdownReq.cs | 2 +- .../Networking/UvWriteRequest.cs | 10 +- src/SampleApp/Program.cs | 3 + .../EngineTests.cs | 202 ++++++++++++++++++ .../Project.json | 4 +- 11 files changed, 280 insertions(+), 85 deletions(-) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs index 6baf4864bb..a5372243e8 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs @@ -45,17 +45,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http ((Connection)state).OnRead(handle, nread); } - - private readonly Func _app; private readonly UvStreamHandle _socket; - private Frame _frame; - private Action _fault; - private Action _frameConsumeCallback; - private Action _receiveAsyncCompleted; - private Frame _receiveAsyncCompletedFrame; - public Connection(ListenerContext context, UvStreamHandle socket) : base(context) { _socket = socket; @@ -64,41 +56,10 @@ namespace Microsoft.AspNet.Server.Kestrel.Http public void Start() { - //_services.Trace.Event(TraceEventType.Start, TraceMessage.Connection); - SocketInput = new SocketInput(Memory); SocketOutput = new SocketOutput(Thread, _socket); - + _frame = new Frame(this); _socket.ReadStart(_allocCallback, _readCallback, this); - - //_fault = ex => { Debug.WriteLine(ex.Message); }; - - //_frameConsumeCallback = (frame, error) => - //{ - // if (error != null) - // { - // _fault(error); - // } - // try - // { - // Go(false, frame); - // } - // catch (Exception ex) - // { - // _fault(ex); - // } - //}; - - //try - //{ - // //_socket.Blocking = false; - // //_socket.NoDelay = true; - // Go(true, null); - //} - //catch (Exception ex) - //{ - // _fault(ex); - //} } private Libuv.uv_buf_t OnAlloc(UvStreamHandle handle, int suggestedSize) @@ -119,10 +80,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http SocketInput.RemoteIntakeFin = true; } - if (_frame == null) - { - _frame = new Frame(this); - } _frame.Consume(); } @@ -141,14 +98,26 @@ namespace Microsoft.AspNet.Server.Kestrel.Http switch (endType) { case ProduceEndType.SocketShutdownSend: - var shutdown = new UvShutdownReq(); - shutdown.Init(Thread.Loop); - shutdown.Shutdown(_socket, (req, status, state) => req.Close(), null); + Thread.Post( + x => + { + var self = (Connection)x; + var shutdown = new UvShutdownReq(); + shutdown.Init(self.Thread.Loop); + shutdown.Shutdown(self._socket, (req, status, state) => req.Close(), null); + }, + this); break; case ProduceEndType.ConnectionKeepAlive: + _frame = new Frame(this); + Thread.Post( + x => ((Frame)x).Consume(), + _frame); break; case ProduceEndType.SocketDisconnect: - _socket.Close(); + Thread.Post( + x => ((UvHandle)x).Close(), + _socket); break; } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs index ca1d2c1bc5..3049e3dc32 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -87,7 +87,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http switch (_mode) { case Mode.StartLine: - if (input.RemoteIntakeFin) + if (input.Buffer.Count == 0 && input.RemoteIntakeFin) { _mode = Mode.Terminated; return; @@ -95,6 +95,10 @@ namespace Microsoft.AspNet.Server.Kestrel.Http if (!TakeStartLine(input)) { + if (input.RemoteIntakeFin) + { + _mode = Mode.Terminated; + } return; } @@ -102,7 +106,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http break; case Mode.MessageHeader: - if (input.RemoteIntakeFin) + if (input.Buffer.Count == 0 && input.RemoteIntakeFin) { _mode = Mode.Terminated; return; @@ -113,16 +117,25 @@ namespace Microsoft.AspNet.Server.Kestrel.Http { if (!TakeMessageHeader(input, out endOfHeaders)) { + if (input.RemoteIntakeFin) + { + _mode = Mode.Terminated; + } return; } } //var resumeBody = HandleExpectContinue(callback); - Execute(); _mode = Mode.MessageBody; + Execute(); break; case Mode.MessageBody: + if (_messageBody.LocalIntakeFin) + { + // NOTE: stop reading and resume on keepalive? + return; + } _messageBody.Consume(); // NOTE: keep looping? return; diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBody.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBody.cs index a2603bf8fb..ecdfc27aad 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBody.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBody.cs @@ -6,24 +6,6 @@ using System.Collections.Generic; namespace Microsoft.AspNet.Server.Kestrel.Http { - public static class DelegateExtensions - { - public static void InvokeNoThrow(this Action d) - { - try - { d.Invoke(); } - catch - { } - } - public static void InvokeNoThrow(this Action d, T arg1) - { - try - { d.Invoke(arg1); } - catch - { } - } - } - public abstract class MessageBody : MessageBodyExchanger { private Action _continuation = () => { }; @@ -156,8 +138,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http var consumeLength = Math.Min(_neededLength, input.Buffer.Count); _neededLength -= consumeLength; - var consumed = input.Take(consumeLength); - if (_neededLength != 0) { Intake(consumeLength); diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs index a9e5dd7676..7f4bf0eaf0 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs @@ -59,6 +59,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_loop_close _uv_loop_close; public void loop_close(UvLoopHandle handle) { + handle.Validate(closed: true); Check(_uv_loop_close(handle.DangerousGetHandle())); } @@ -67,6 +68,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_run _uv_run; public int run(UvLoopHandle handle, int mode) { + handle.Validate(); return Check(_uv_run(handle, mode)); } @@ -75,6 +77,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_stop _uv_stop; public void stop(UvLoopHandle handle) { + handle.Validate(); _uv_stop(handle); } @@ -83,6 +86,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_ref _uv_ref; public void @ref(UvHandle handle) { + handle.Validate(); _uv_ref(handle); } @@ -91,6 +95,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_unref _uv_unref; public void unref(UvHandle handle) { + handle.Validate(); _uv_unref(handle); } @@ -102,6 +107,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_close _uv_close; public void close(UvHandle handle, uv_close_cb close_cb) { + handle.Validate(closed: true); _uv_close(handle.DangerousGetHandle(), close_cb); } @@ -112,6 +118,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_async_init _uv_async_init; public void async_init(UvLoopHandle loop, UvAsyncHandle handle, uv_async_cb cb) { + loop.Validate(); + handle.Validate(); Check(_uv_async_init(loop, handle, cb)); } @@ -128,6 +136,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_tcp_init _uv_tcp_init; public void tcp_init(UvLoopHandle loop, UvTcpHandle handle) { + loop.Validate(); + handle.Validate(); Check(_uv_tcp_init(loop, handle)); } @@ -136,6 +146,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_tcp_bind _uv_tcp_bind; public void tcp_bind(UvTcpHandle handle, ref sockaddr addr, int flags) { + handle.Validate(); Check(_uv_tcp_bind(handle, ref addr, flags)); } @@ -146,6 +157,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_listen _uv_listen; public void listen(UvStreamHandle handle, int backlog, uv_connection_cb cb) { + handle.Validate(); Check(_uv_listen(handle, backlog, cb)); } @@ -154,6 +166,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_accept _uv_accept; public void accept(UvStreamHandle server, UvStreamHandle client) { + server.Validate(); + client.Validate(); Check(_uv_accept(server, client)); } @@ -166,6 +180,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_read_start _uv_read_start; public void read_start(UvStreamHandle handle, uv_alloc_cb alloc_cb, uv_read_cb read_cb) { + handle.Validate(); Check(_uv_read_start(handle, alloc_cb, read_cb)); } @@ -174,6 +189,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_read_stop _uv_read_stop; public void read_stop(UvStreamHandle handle) { + handle.Validate(); Check(_uv_read_stop(handle)); } @@ -182,6 +198,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_try_write _uv_try_write; public int try_write(UvStreamHandle handle, Libuv.uv_buf_t[] bufs, int nbufs) { + handle.Validate(); return Check(_uv_try_write(handle, bufs, nbufs)); } @@ -192,6 +209,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_write _uv_write; public void write(UvWriteReq req, UvStreamHandle handle, Libuv.uv_buf_t[] bufs, int nbufs, uv_write_cb cb) { + req.Validate(); + handle.Validate(); Check(_uv_write(req, handle, bufs, nbufs, cb)); } @@ -202,6 +221,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking uv_shutdown _uv_shutdown; public void shutdown(UvShutdownReq req, UvStreamHandle handle, uv_shutdown_cb cb) { + req.Validate(); + handle.Validate(); Check(_uv_shutdown(req, handle, cb)); } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs index e76dac9176..d08a6910d7 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs @@ -9,8 +9,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking { static Libuv.uv_close_cb _close_cb = DestroyHandle; - - protected override bool ReleaseHandle() { var memory = handle; diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemory.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemory.cs index 145b286fe1..a970b1e053 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemory.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemory.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - +#define TRACE using System; +using System.Diagnostics; using System.Runtime.InteropServices; +using System.Threading; namespace Microsoft.AspNet.Server.Kestrel.Networking { @@ -12,6 +14,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking public abstract class UvMemory : SafeHandle { protected Libuv _uv; + int _threadId; + public UvMemory() : base(IntPtr.Zero, true) { } @@ -29,6 +33,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking unsafe protected void CreateHandle(Libuv uv, int size) { _uv = uv; + _threadId = Thread.CurrentThread.ManagedThreadId; + handle = Marshal.AllocCoTaskMem(size); *(IntPtr*)handle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); } @@ -36,8 +42,17 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking protected void CreateHandle(UvLoopHandle loop, int size) { CreateHandle(loop._uv, size); + _threadId = loop._threadId; } + public void Validate(bool closed = false) + { + Trace.Assert(IsClosed == closed, "Handle is closed"); + Trace.Assert(!IsInvalid, "Handle is invalid"); + Trace.Assert(_threadId == Thread.CurrentThread.ManagedThreadId, "ThreadId is correct"); + } + + unsafe protected static void DestroyHandle(IntPtr memory) { var gcHandlePtr = *(IntPtr*)memory; diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvShutdownReq.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvShutdownReq.cs index 2bc23468e2..05496b3a06 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvShutdownReq.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvShutdownReq.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking public void Init(UvLoopHandle loop) { - CreateHandle(loop.Libuv, loop.Libuv.req_size(3)); + CreateHandle(loop, loop.Libuv.req_size(3)); } public void Shutdown(UvStreamHandle handle, Action callback, object state) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteRequest.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteRequest.cs index 17381696c5..54d69a3580 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteRequest.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteRequest.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking public void Init(UvLoopHandle loop) { - CreateHandle(loop.Libuv, loop.Libuv.req_size(2)); + CreateHandle(loop, loop.Libuv.req_size(2)); } public void Write(UvStreamHandle handle, Libuv.uv_buf_t[] bufs, int nbufs, Action callback, object state) @@ -39,14 +39,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking public abstract class UvReq : UvMemory { - - unsafe protected void CreateHandle(Libuv uv, int size) - { - _uv = uv; - handle = Marshal.AllocCoTaskMem(size); - *(IntPtr*)handle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); - } - protected override bool ReleaseHandle() { DestroyHandle(handle); diff --git a/src/SampleApp/Program.cs b/src/SampleApp/Program.cs index 446628b570..21673112e6 100644 --- a/src/SampleApp/Program.cs +++ b/src/SampleApp/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using System.Threading.Tasks; namespace SampleApp @@ -17,6 +18,8 @@ namespace SampleApp engine.Stop(); } + + private static async Task App(object arg) { var httpContext = new Microsoft.AspNet.PipelineCore.DefaultHttpContext( diff --git a/test/Microsoft.AspNet.Server.KestralTests/EngineTests.cs b/test/Microsoft.AspNet.Server.KestralTests/EngineTests.cs index 4c5b7e6887..3fe9f291d3 100644 --- a/test/Microsoft.AspNet.Server.KestralTests/EngineTests.cs +++ b/test/Microsoft.AspNet.Server.KestralTests/EngineTests.cs @@ -1,6 +1,7 @@ using Microsoft.AspNet.HttpFeature; using Microsoft.AspNet.Server.Kestrel; using System; +using System.IO; using System.Net; using System.Net.Sockets; using System.Text; @@ -29,6 +30,25 @@ namespace Microsoft.AspNet.Server.KestralTests await response.Body.WriteAsync(buffer, 0, count); } } + private async Task AppChunked(object callContext) + { + var request = callContext as IHttpRequestFeature; + var response = callContext as IHttpResponseFeature; + response.Headers["Transfer-Encoding"] = new[] { "chunked" }; + for (; ;) + { + var buffer = new byte[8192]; + var count = await request.Body.ReadAsync(buffer, 0, buffer.Length); + var hex = Encoding.ASCII.GetBytes(count.ToString("x") + "\r\n"); + await response.Body.WriteAsync(hex, 0, hex.Length); + if (count == 0) + { + break; + } + await response.Body.WriteAsync(buffer, 0, count); + await response.Body.WriteAsync(new[] { (byte)'\r', (byte)'\n' }, 0, 2); + } + } [Fact] public async Task EngineCanStartAndStop() @@ -70,5 +90,187 @@ namespace Microsoft.AspNet.Server.KestralTests started.Dispose(); engine.Stop(); } + + [Fact] + public async Task Http10() + { + var engine = new KestrelEngine(); + engine.Start(1); + var started = engine.CreateServer(App); + + Transceive( +@"POST / HTTP/1.0 + +Hello World", +@"HTTP/1.0 200 OK + +Hello World"); + started.Dispose(); + engine.Stop(); + } + + [Fact] + public async Task Http10ContentLength() + { + var engine = new KestrelEngine(); + engine.Start(1); + var started = engine.CreateServer(App); + + Transceive( +@"POST / HTTP/1.0 +Content-Length: 5 + +Hello World", +@"HTTP/1.0 200 OK + +Hello"); + started.Dispose(); + engine.Stop(); + } + + [Fact] + public async Task Http10TransferEncoding() + { + var engine = new KestrelEngine(); + engine.Start(1); + var started = engine.CreateServer(App); + + Transceive( +@"POST / HTTP/1.0 +Transfer-Encoding: chunked + +5 +Hello +6 + World +0 +ignored", +@"HTTP/1.0 200 OK + +Hello World"); + started.Dispose(); + engine.Stop(); + } + + + [Fact] + public async Task Http10KeepAlive() + { + var engine = new KestrelEngine(); + engine.Start(1); + var started = engine.CreateServer(AppChunked); + + Transceive( +@"GET / HTTP/1.0 +Connection: Keep-Alive + +POST / HTTP/1.0 + +Goodbye", +@"HTTP/1.0 200 OK +Transfer-Encoding: chunked +Connection: keep-alive + +0 +HTTP/1.0 200 OK +Transfer-Encoding: chunked + +7 +Goodbye +0 +"); + started.Dispose(); + engine.Stop(); + } + + [Fact] + public async Task Http10KeepAliveContentLength() + { + var engine = new KestrelEngine(); + engine.Start(1); + var started = engine.CreateServer(AppChunked); + + Transceive( +@"POST / HTTP/1.0 +Connection: Keep-Alive +Content-Length: 11 + +Hello WorldPOST / HTTP/1.0 + +Goodbye", +@"HTTP/1.0 200 OK +Transfer-Encoding: chunked +Connection: keep-alive + +b +Hello World +0 +HTTP/1.0 200 OK +Transfer-Encoding: chunked + +7 +Goodbye +0 +"); + started.Dispose(); + engine.Stop(); + } + + [Fact] + public async Task Http10KeepAliveTransferEncoding() + { + var engine = new KestrelEngine(); + engine.Start(1); + var started = engine.CreateServer(AppChunked); + + Transceive( +@"POST / HTTP/1.0 +Transfer-Encoding: chunked +Connection: keep-alive + +5 +Hello +6 + World +0 +POST / HTTP/1.0 + +Goodbye", +@"HTTP/1.0 200 OK +Transfer-Encoding: chunked +Connection: keep-alive + +b +Hello World +0 +HTTP/1.0 200 OK +Transfer-Encoding: chunked + +7 +Goodbye +0 +"); + 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); + var writer = new StreamWriter(stream, Encoding.ASCII); + writer.Write(send); + writer.Flush(); + stream.Flush(); + socket.Shutdown(SocketShutdown.Send); + + var reader = new StreamReader(stream, Encoding.ASCII); + var actual = reader.ReadToEnd(); + + Assert.Equal(expected, actual); + } } } diff --git a/test/Microsoft.AspNet.Server.KestralTests/Project.json b/test/Microsoft.AspNet.Server.KestralTests/Project.json index 131b7d78c1..748dedce43 100644 --- a/test/Microsoft.AspNet.Server.KestralTests/Project.json +++ b/test/Microsoft.AspNet.Server.KestralTests/Project.json @@ -7,7 +7,9 @@ "Microsoft.AspNet.Server.Kestrel": "0.1-*" }, "configurations": { - "net45": {} + "net45": { + "compilationOptions": { "define": [ "TRACE" ] } + } }, "commands": { "run": "Xunit.KRunner",