From e517e39aac7a2f2f143b237b556c7e490a66c121 Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Fri, 6 Jun 2014 22:13:31 -0700 Subject: [PATCH] Not working, but worth taking a snapshot of the source --- KestrelHttpServer.sln | 6 + .../Http/CallContext.cs | 51 ++ .../Http/Connection.cs | 159 ++++++ .../Http/Frame.cs | 540 ++++++++++++++++++ .../Http/FrameDuplexStream.cs | 166 ++++++ .../Http/FrameRequestStream.cs | 94 +++ .../Http/FrameResponseStream.cs | 93 +++ .../Http/Listener.cs | 82 +-- .../Http/MemoryPool.cs | 154 +++++ .../Http/MemoryPoolTextWriter.cs | 155 +++++ .../Http/MessageBody.cs | 342 +++++++++++ .../Http/MessageBodyExchanger.cs | 144 +++++ .../Http/ReasonPhrases.cs | 133 +++++ .../Http/SocketInput.cs | 106 ++++ .../Http/SocketOutput.cs | 106 ++++ .../Infrastructure/Disposable.cs | 5 +- .../Infrastructure/KestrelThread.cs | 5 +- .../KestrelEngine.cs | 11 +- .../Microsoft.AspNet.Server.Kestrel.kproj | 57 ++ .../Networking/Libuv.cs | 48 ++ .../Networking/UvHandle.cs | 41 +- .../Networking/UvLoopHandle.cs | 1 - .../Networking/UvMemory.cs | 58 ++ .../Networking/UvShutdownReq.cs | 38 ++ .../Networking/UvStreamHandle.cs | 43 +- .../Networking/UvWriteRequest.cs | 57 ++ .../Project.json | 1 + src/SampleApp/Program.cs | 36 ++ src/SampleApp/SampleApp.kproj | 35 ++ src/SampleApp/libuv.dll | Bin 0 -> 317440 bytes src/SampleApp/project.json | 14 + .../EngineTests.cs | 52 +- .../MessageBodyExchangerTests.cs | 190 ++++++ .../MessageBodyTests.cs | 64 +++ ...Microsoft.AspNet.Server.KestralTests.kproj | 37 ++ .../NetworkingTests.cs | 19 +- .../TestInput.cs | 34 ++ 37 files changed, 3065 insertions(+), 112 deletions(-) create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Http/CallContext.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Http/FrameDuplexStream.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Http/FrameRequestStream.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Http/FrameResponseStream.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPool.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolTextWriter.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Http/MessageBody.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Http/MessageBodyExchanger.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Http/ReasonPhrases.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Http/SocketInput.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Microsoft.AspNet.Server.Kestrel.kproj create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemory.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Networking/UvShutdownReq.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteRequest.cs create mode 100644 src/SampleApp/Program.cs create mode 100644 src/SampleApp/SampleApp.kproj create mode 100644 src/SampleApp/libuv.dll create mode 100644 src/SampleApp/project.json create mode 100644 test/Microsoft.AspNet.Server.KestralTests/MessageBodyExchangerTests.cs create mode 100644 test/Microsoft.AspNet.Server.KestralTests/MessageBodyTests.cs create mode 100644 test/Microsoft.AspNet.Server.KestralTests/Microsoft.AspNet.Server.KestralTests.kproj create mode 100644 test/Microsoft.AspNet.Server.KestralTests/TestInput.cs diff --git a/KestrelHttpServer.sln b/KestrelHttpServer.sln index 9b0ccec28c..fbfe94da08 100644 --- a/KestrelHttpServer.sln +++ b/KestrelHttpServer.sln @@ -12,6 +12,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution global.json = global.json EndProjectSection EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SampleApp", "src\SampleApp\SampleApp.kproj", "{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -26,6 +28,10 @@ Global {37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Debug|Any CPU.Build.0 = Debug|Any CPU {37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Release|Any CPU.ActiveCfg = Release|Any CPU {37F3BFB2-6454-49E5-9D7F-581BF755CCFE}.Release|Any CPU.Build.0 = Release|Any CPU + {2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/CallContext.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/CallContext.cs new file mode 100644 index 0000000000..39b11b7ac3 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/CallContext.cs @@ -0,0 +1,51 @@ +// 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. + +using Microsoft.AspNet.HttpFeature; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Microsoft.AspNet.Server.Kestrel +{ + /// + /// Summary description for CallContext + /// + public class CallContext : + IHttpRequestFeature, + IHttpResponseFeature + { + public CallContext() + { + ((IHttpResponseFeature)this).StatusCode = 200; + } + + Stream IHttpResponseFeature.Body { get; set; } + + Stream IHttpRequestFeature.Body { get; set; } + + IDictionary IHttpResponseFeature.Headers { get; set; } + + IDictionary IHttpRequestFeature.Headers { get; set; } + + string IHttpRequestFeature.Method { get; set; } + + string IHttpRequestFeature.Path { get; set; } + + string IHttpRequestFeature.PathBase { get; set; } + + string IHttpRequestFeature.Protocol { get; set; } + + string IHttpRequestFeature.QueryString { get; set; } + + string IHttpResponseFeature.ReasonPhrase { get; set; } + + string IHttpRequestFeature.Scheme { get; set; } + + int IHttpResponseFeature.StatusCode { get; set; } + + void IHttpResponseFeature.OnSendingHeaders(Action callback, object state) + { + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs new file mode 100644 index 0000000000..89837db215 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs @@ -0,0 +1,159 @@ +// 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. + +using System; +using System.Diagnostics; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.Server.Kestrel.Networking; + +namespace Microsoft.AspNet.Server.Kestrel.Http +{ + public class ConnectionContext : ListenerContext + { + public ConnectionContext() + { + } + + public ConnectionContext(ListenerContext context) : base(context) + { + } + + public SocketInput SocketInput { get; set; } + public ISocketOutput SocketOutput { get; set; } + + public IConnectionControl ConnectionControl { get; set; } + } + + public interface IConnectionControl + { + void Pause(); + void Resume(); + void End(ProduceEndType endType); + } + + public class Connection : ConnectionContext, IConnectionControl + { + private static readonly Action _readCallback = ReadCallback; + private static readonly Func _allocCallback = AllocCallback; + + private static Libuv.uv_buf_t AllocCallback(UvStreamHandle handle, int suggestedSize, object state) + { + return ((Connection)state).OnAlloc(handle, suggestedSize); + } + + private static void ReadCallback(UvStreamHandle handle, int nread, object state) + { + ((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; + ConnectionControl = this; + } + + public void Start() + { + //_services.Trace.Event(TraceEventType.Start, TraceMessage.Connection); + + SocketInput = new SocketInput(Memory); + SocketOutput = new SocketOutput(Thread, _socket); + + _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) + { + return new Libuv.uv_buf_t + { + memory = SocketInput.Pin(2048), + len = 2048 + }; + } + + private void OnRead(UvStreamHandle handle, int nread) + { + SocketInput.Unpin(nread); + + if (nread == 0) + { + SocketInput.RemoteIntakeFin = true; + } + + if (_frame == null) + { + _frame = new Frame(this); + } + _frame.Consume(); + } + + void IConnectionControl.Pause() + { + _socket.ReadStop(); + } + + void IConnectionControl.Resume() + { + _socket.ReadStart(_allocCallback, _readCallback, this); + } + + void IConnectionControl.End(ProduceEndType endType) + { + switch (endType) + { + case ProduceEndType.SocketShutdownSend: + var shutdown = new UvShutdownReq(); + shutdown.Init(Thread.Loop); + shutdown.Shutdown(_socket, (req, status, state) => req.Close(), null); + break; + case ProduceEndType.ConnectionKeepAlive: + break; + case ProduceEndType.SocketDisconnect: + _socket.Close(); + break; + } + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs new file mode 100644 index 0000000000..0697149351 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -0,0 +1,540 @@ +// 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. + +using Microsoft.AspNet.HttpFeature; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +// ReSharper disable AccessToModifiedClosure + +namespace Microsoft.AspNet.Server.Kestrel.Http +{ + + public enum ProduceEndType + { + SocketShutdownSend, + SocketDisconnect, + ConnectionKeepAlive, + } + + public class Frame + { + private ConnectionContext _context; + + Mode _mode; + + enum Mode + { + StartLine, + MessageHeader, + MessageBody, + Terminated, + } + + + private string _method; + private string _requestUri; + private string _path; + private string _queryString; + private string _httpVersion; + + private readonly IDictionary _requestHeaders = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + readonly IDictionary _responseHeaders = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + private MessageBody _messageBody; + private bool _resultStarted; + private bool _keepAlive; + + private CallContext _callContext; + /* + //IDictionary _environment; + + CancellationTokenSource _cts = new CancellationTokenSource(); + */ + FrameResponseStream _outputStream; + FrameRequestStream _inputStream; + FrameDuplexStream _duplexStream; + + Task _upgradeTask = _completedTask; + static readonly Task _completedTask = Task.FromResult(0); + + public Frame(ConnectionContext context) + { + _context = context; + } + /* + public bool LocalIntakeFin + { + get + { + return _mode == Mode.MessageBody + ? _messageBody.LocalIntakeFin + : _mode == Mode.Terminated; + } + } + */ + public void Consume() + { + var input = _context.SocketInput; + for (; ;) + { + switch (_mode) + { + case Mode.StartLine: + if (input.RemoteIntakeFin) + { + _mode = Mode.Terminated; + return; + } + + if (!TakeStartLine(input)) + { + return; + } + + _mode = Mode.MessageHeader; + break; + + case Mode.MessageHeader: + if (input.RemoteIntakeFin) + { + _mode = Mode.Terminated; + return; + } + + var endOfHeaders = false; + while (!endOfHeaders) + { + if (!TakeMessageHeader(input, out endOfHeaders)) + { + return; + } + } + + //var resumeBody = HandleExpectContinue(callback); + Execute(); + _mode = Mode.MessageBody; + break; + + case Mode.MessageBody: + _messageBody.Consume(); + // NOTE: keep looping? + return; + + case Mode.Terminated: + return; + } + } + } + + Action HandleExpectContinue(Action continuation) + { + string[] expect; + if (_httpVersion.Equals("HTTP/1.1") && + _requestHeaders.TryGetValue("Expect", out expect) && + (expect.FirstOrDefault() ?? "").Equals("100-continue", StringComparison.OrdinalIgnoreCase)) + { + return (frame, error) => + { + if (_resultStarted) + { + continuation.Invoke(frame, error); + } + else + { + var bytes = Encoding.Default.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"); + + //var isasync = _context.SocketOutput.Write( + // new ArraySegment(bytes), + // error2 => continuation(frame, error2)); + + //if (!isasync) + //{ + // continuation.Invoke(frame, null); + //} + } + }; + } + return continuation; + } + + private void Execute() + { + _messageBody = MessageBody.For( + _httpVersion, + _requestHeaders, + _context); + _keepAlive = _messageBody.RequestKeepAlive; + _callContext = CreateCallContext(); + _context.SocketInput.Free(); + Task.Run(ExecuteAsync); + } + + private async Task ExecuteAsync() + { + Exception error = null; + try + { + await _context.Application.Invoke(_callContext); + await _upgradeTask; + } + catch (Exception ex) + { + error = ex; + } + finally + { + ProduceEnd(error); + } + } + + private CallContext CreateCallContext() + { + _inputStream = new FrameRequestStream(_messageBody); + _outputStream = new FrameResponseStream(OnWrite); + _duplexStream = new FrameDuplexStream(_inputStream, _outputStream); + + var remoteIpAddress = "127.0.0.1"; + var remotePort = "0"; + var localIpAddress = "127.0.0.1"; + var localPort = "80"; + var isLocal = false; + + //if (_context.Socket != null) + //{ + // var remoteEndPoint = _context.Socket.RemoteEndPoint as IPEndPoint; + // if (remoteEndPoint != null) + // { + // remoteIpAddress = remoteEndPoint.Address.ToString(); + // remotePort = remoteEndPoint.Port.ToString(CultureInfo.InvariantCulture); + // } + + // var localEndPoint = _context.Socket.LocalEndPoint as IPEndPoint; + // if (localEndPoint != null) + // { + // localIpAddress = localEndPoint.Address.ToString(); + // localPort = localEndPoint.Port.ToString(CultureInfo.InvariantCulture); + // } + + // if (remoteEndPoint != null && localEndPoint != null) + // { + // isLocal = Equals(remoteEndPoint.Address, localEndPoint.Address); + // } + //} + + var callContext = new CallContext(); + var request = (IHttpRequestFeature)callContext; + var response = (IHttpResponseFeature)callContext; + //var lifetime = (IHttpRequestLifetimeFeature)callContext; + request.Protocol = _httpVersion; + request.Scheme = "http"; + request.Method = _method; + request.Path = _path; + request.PathBase = ""; + request.QueryString = _queryString; + request.Headers = _requestHeaders; + request.Body = _inputStream; + response.Headers = _responseHeaders; + response.Body = _outputStream; + + //var env = new Dictionary(); + //env["owin.Version"] = "1.0"; + //env["owin.RequestProtocol"] = _httpVersion; + //env["owin.RequestScheme"] = "http"; + //env["owin.RequestMethod"] = _method; + //env["owin.RequestPath"] = _path; + //env["owin.RequestPathBase"] = ""; + //env["owin.RequestQueryString"] = _queryString; + //env["owin.RequestHeaders"] = _requestHeaders; + //env["owin.RequestBody"] = _inputStream; + //env["owin.ResponseHeaders"] = _responseHeaders; + //env["owin.ResponseBody"] = _outputStream; + //env["owin.CallCancelled"] = _cts.Token; + //env["opaque.Upgrade"] = (Action, Func, Task>>)Upgrade; + //env["opaque.Stream"] = _duplexStream; + //env["server.RemoteIpAddress"] = remoteIpAddress; + //env["server.RemotePort"] = remotePort; + //env["server.LocalIpAddress"] = localIpAddress; + //env["server.LocalPort"] = localPort; + //env["server.IsLocal"] = isLocal; + return callContext; + } + + void OnWrite(ArraySegment data, Action callback, object state) + { + ProduceStart(); + _context.SocketOutput.Write(data, callback, state); + } + + void Upgrade(IDictionary options, Func callback) + { + _keepAlive = false; + ProduceStart(); + + _upgradeTask = callback(_callContext); + } + + void ProduceStart() + { + if (_resultStarted) return; + + _resultStarted = true; + + var response = (IHttpResponseFeature)_callContext; + var status = ReasonPhrases.ToStatus( + response.StatusCode, + response.ReasonPhrase); + + var responseHeader = CreateResponseHeader(status, _responseHeaders); + _context.SocketOutput.Write(responseHeader.Item1, x => ((IDisposable)x).Dispose(), responseHeader.Item2); + } + + private void ProduceEnd(Exception ex) + { + ProduceStart(); + + if (!_keepAlive) + { + _context.ConnectionControl.End(ProduceEndType.SocketShutdownSend); + } + + _messageBody.Drain(() => + _context.ConnectionControl.End(_keepAlive ? ProduceEndType.ConnectionKeepAlive : ProduceEndType.SocketDisconnect)); + } + + + private Tuple, IDisposable> CreateResponseHeader( + string status, IEnumerable> headers) + { + var writer = new MemoryPoolTextWriter(_context.Memory); + writer.Write(_httpVersion); + writer.Write(' '); + writer.Write(status); + writer.Write('\r'); + writer.Write('\n'); + + var hasConnection = false; + var hasTransferEncoding = false; + var hasContentLength = false; + if (headers != null) + { + foreach (var header in headers) + { + var isConnection = false; + if (!hasConnection && + string.Equals(header.Key, "Connection", StringComparison.OrdinalIgnoreCase)) + { + hasConnection = isConnection = true; + } + else if (!hasTransferEncoding && + string.Equals(header.Key, "Transfer-Encoding", StringComparison.OrdinalIgnoreCase)) + { + hasTransferEncoding = true; + } + else if (!hasContentLength && + string.Equals(header.Key, "Content-Length", StringComparison.OrdinalIgnoreCase)) + { + hasContentLength = true; + } + + foreach (var value in header.Value) + { + writer.Write(header.Key); + writer.Write(':'); + writer.Write(' '); + writer.Write(value); + writer.Write('\r'); + writer.Write('\n'); + + if (isConnection && value.IndexOf("close", StringComparison.OrdinalIgnoreCase) != -1) + { + _keepAlive = false; + } + } + } + } + + if (hasTransferEncoding == false && hasContentLength == false) + { + _keepAlive = false; + } + if (_keepAlive == false && hasConnection == false && _httpVersion == "HTTP/1.1") + { + writer.Write("Connection: close\r\n\r\n"); + } + else if (_keepAlive && hasConnection == false && _httpVersion == "HTTP/1.0") + { + writer.Write("Connection: keep-alive\r\n\r\n"); + } + else + { + writer.Write('\r'); + writer.Write('\n'); + } + writer.Flush(); + return new Tuple, IDisposable>(writer.Buffer, writer); + } + + private bool TakeStartLine(SocketInput baton) + { + var remaining = baton.Buffer; + if (remaining.Count < 2) + { + return false; + } + var firstSpace = -1; + var secondSpace = -1; + var questionMark = -1; + var ch0 = remaining.Array[remaining.Offset]; + for (var index = 0; index != remaining.Count - 1; ++index) + { + var ch1 = remaining.Array[remaining.Offset + index + 1]; + if (ch0 == '\r' && ch1 == '\n') + { + if (secondSpace == -1) + { + throw new InvalidOperationException("INVALID REQUEST FORMAT"); + } + _method = GetString(remaining, 0, firstSpace); + _requestUri = GetString(remaining, firstSpace + 1, secondSpace); + if (questionMark == -1) + { + _path = _requestUri; + _queryString = string.Empty; + } + else + { + _path = GetString(remaining, firstSpace + 1, questionMark); + _queryString = GetString(remaining, questionMark, secondSpace); + } + _httpVersion = GetString(remaining, secondSpace + 1, index); + baton.Skip(index + 2); + return true; + } + + if (ch0 == ' ' && firstSpace == -1) + { + firstSpace = index; + } + else if (ch0 == ' ' && firstSpace != -1 && secondSpace == -1) + { + secondSpace = index; + } + else if (ch0 == '?' && firstSpace != -1 && questionMark == -1 && secondSpace == -1) + { + questionMark = index; + } + ch0 = ch1; + } + return false; + } + + static string GetString(ArraySegment range, int startIndex, int endIndex) + { + return Encoding.Default.GetString(range.Array, range.Offset + startIndex, endIndex - startIndex); + } + + + private bool TakeMessageHeader(SocketInput baton, out bool endOfHeaders) + { + var remaining = baton.Buffer; + endOfHeaders = false; + if (remaining.Count < 2) + { + return false; + } + var ch0 = remaining.Array[remaining.Offset]; + var ch1 = remaining.Array[remaining.Offset + 1]; + if (ch0 == '\r' && ch1 == '\n') + { + endOfHeaders = true; + baton.Skip(2); + return true; + } + + if (remaining.Count < 3) + { + return false; + } + var wrappedHeaders = false; + var colonIndex = -1; + var valueStartIndex = -1; + var valueEndIndex = -1; + for (var index = 0; index != remaining.Count - 2; ++index) + { + var ch2 = remaining.Array[remaining.Offset + index + 2]; + if (ch0 == '\r' && + ch1 == '\n' && + ch2 != ' ' && + ch2 != '\t') + { + var name = Encoding.ASCII.GetString(remaining.Array, remaining.Offset, colonIndex); + var value = ""; + if (valueEndIndex != -1) + { + value = Encoding.ASCII.GetString( + remaining.Array, remaining.Offset + valueStartIndex, valueEndIndex - valueStartIndex); + } + if (wrappedHeaders) + { + value = value.Replace("\r\n", " "); + } + AddRequestHeader(name, value); + baton.Skip(index + 2); + return true; + } + if (colonIndex == -1 && ch0 == ':') + { + colonIndex = index; + } + else if (colonIndex != -1 && + ch0 != ' ' && + ch0 != '\t' && + ch0 != '\r' && + ch0 != '\n') + { + if (valueStartIndex == -1) + { + valueStartIndex = index; + } + valueEndIndex = index + 1; + } + else if (!wrappedHeaders && + ch0 == '\r' && + ch1 == '\n' && + (ch2 == ' ' || + ch2 == '\t')) + { + wrappedHeaders = true; + } + + ch0 = ch1; + ch1 = ch2; + } + return false; + } + + private void AddRequestHeader(string name, string value) + { + string[] existing; + if (!_requestHeaders.TryGetValue(name, out existing) || + existing == null || + existing.Length == 0) + { + _requestHeaders[name] = new[] { value }; + } + else + { + _requestHeaders[name] = existing.Concat(new[] { value }).ToArray(); + } + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/FrameDuplexStream.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/FrameDuplexStream.cs new file mode 100644 index 0000000000..7028eacdb4 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/FrameDuplexStream.cs @@ -0,0 +1,166 @@ +// 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. + +using System; +using System.IO; + +namespace Microsoft.AspNet.Server.Kestrel.Http +{ + class FrameDuplexStream : Stream + { + readonly FrameRequestStream _requestStream; + readonly FrameResponseStream _responseStream; + + public FrameDuplexStream(FrameRequestStream requestStream, FrameResponseStream responseStream) + { + _requestStream = requestStream; + _responseStream = responseStream; + } + + public override void Close() + { + _requestStream.Close(); + _responseStream.Close(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _requestStream.Dispose(); + _responseStream.Dispose(); + } + } + + public override void Flush() + { + _responseStream.Flush(); + } + + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return _requestStream.BeginRead(buffer, offset, count, callback, state); + } + + public override int EndRead(IAsyncResult asyncResult) + { + return _requestStream.EndRead(asyncResult); + } + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return _responseStream.BeginWrite(buffer, offset, count, callback, state); + } + + public override void EndWrite(IAsyncResult asyncResult) + { + _responseStream.EndWrite(asyncResult); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return _requestStream.Seek(offset, origin); + } + + public override void SetLength(long value) + { + _requestStream.SetLength(value); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _requestStream.Read(buffer, offset, count); + } + + public override int ReadByte() + { + return _requestStream.ReadByte(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + _responseStream.Write(buffer, offset, count); + } + + public override void WriteByte(byte value) + { + _responseStream.WriteByte(value); + } + + public override bool CanRead + { + get + { + return _requestStream.CanRead; + } + } + + public override bool CanSeek + { + get + { + return _requestStream.CanSeek; + } + } + + public override bool CanTimeout + { + get + { + return _responseStream.CanTimeout || _requestStream.CanTimeout; + } + } + + public override bool CanWrite + { + get + { + return _responseStream.CanWrite; + } + } + + public override long Length + { + get + { + return _requestStream.Length; + } + } + + public override long Position + { + get + { + return _requestStream.Position; + } + set + { + _requestStream.Position = value; + } + } + + public override int ReadTimeout + { + get + { + return _requestStream.ReadTimeout; + } + set + { + _requestStream.ReadTimeout = value; + } + } + + public override int WriteTimeout + { + get + { + return _responseStream.WriteTimeout; + } + set + { + _responseStream.WriteTimeout = value; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/FrameRequestStream.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/FrameRequestStream.cs new file mode 100644 index 0000000000..16a4c90920 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/FrameRequestStream.cs @@ -0,0 +1,94 @@ +// 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. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Server.Kestrel.Http +{ + public class FrameRequestStream : Stream + { + readonly MessageBody _body; + + //int _readLength; + //bool _readFin; + //Exception _readError; + + public FrameRequestStream(MessageBody body) + { + _body = body; + } + + public override void Flush() + { + throw new NotImplementedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return ReadAsync(buffer, offset, count).Result; + } + + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + var task = ReadAsync(buffer, offset, count, CancellationToken.None, state); + if (callback != null) + { + task.ContinueWith(t => callback.Invoke(t)); + } + return task; + } + + public override int EndRead(IAsyncResult asyncResult) + { + return ((Task)asyncResult).Result; + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _body.ReadAsync(new ArraySegment(buffer, offset, count)); + } + + public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) + { + //NOTE todo + throw new NotImplementedException(); + //var tcs = new TaskCompletionSource(state); + //_body.ReadAsync(new ArraySegment(buffer, offset, count)); + //return tcs.Task; + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + public override bool CanRead { get { return true; } } + + public override bool CanSeek { get { return false; } } + + public override bool CanWrite { get { return false; } } + + public override long Length + { + get + { + throw new NotImplementedException(); + } + } + + public override long Position { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/FrameResponseStream.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/FrameResponseStream.cs new file mode 100644 index 0000000000..d4b34fbce6 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/FrameResponseStream.cs @@ -0,0 +1,93 @@ +// 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. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Server.Kestrel.Http +{ + class FrameResponseStream : Stream + { + readonly Action, Action, object> _write; + + public FrameResponseStream(Action, Action, object> write) + { + _write = write; + } + + public override void Flush() + { + //_write(default(ArraySegment), null); + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + _write(new ArraySegment(new byte[0]), x => ((TaskCompletionSource)x).SetResult(0), tcs); + return tcs.Task; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + _write(new ArraySegment(buffer, offset, count), x => ((TaskCompletionSource)x).SetResult(0), tcs); + return tcs.Task; + } + + public override bool CanRead + { + get + { + return false; + } + } + + public override bool CanSeek + { + get + { + return false; + } + } + + public override bool CanWrite + { + get + { + return true; + } + } + + public override long Length + { + get + { + throw new NotImplementedException(); + } + } + + public override long Position { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Listener.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Listener.cs index 720db481fe..95995f9100 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Listener.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Listener.cs @@ -1,36 +1,58 @@ -using Microsoft.AspNet.Server.Kestrel.Networking; +// 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. + +using Microsoft.AspNet.Server.Kestrel.Networking; using System; -using System.Collections.Generic; using System.Net; using System.Text; -using System.Threading; using System.Threading.Tasks; -namespace Microsoft.AspNet.Server.Kestrel +namespace Microsoft.AspNet.Server.Kestrel.Http { + public class ListenerContext + { + public ListenerContext() { } + + public ListenerContext(ListenerContext context) + { + Thread = context.Thread; + Application = context.Application; + Memory = context.Memory; + } + + public KestrelThread Thread { get; set; } + + public Func Application { get; set; } + + public IMemoryPool Memory { get; set; } + } + /// /// Summary description for Accept /// - public class Listener : IDisposable + public class Listener : ListenerContext, IDisposable { - private readonly KestrelThread _thread; - UvTcpHandle _socket; - private readonly Action _connectionCallback = ConnectionCallback; + private static readonly Action _connectionCallback = ConnectionCallback; + + UvTcpHandle ListenSocket { get; set; } private static void ConnectionCallback(UvStreamHandle stream, int status, object state) { ((Listener)state).OnConnection(stream, status); } - public Listener(KestrelThread thread) + public Listener(IMemoryPool memory) { - _thread = thread; + Memory = memory; } - public Task StartAsync() + public Task StartAsync(KestrelThread thread, Func app) { + Thread = thread; + Application = app; + var tcs = new TaskCompletionSource(); - _thread.Post(OnStart, tcs); + Thread.Post(OnStart, tcs); return tcs.Task; } @@ -39,10 +61,10 @@ namespace Microsoft.AspNet.Server.Kestrel var tcs = (TaskCompletionSource)parameter; try { - _socket = new UvTcpHandle(); - _socket.Init(_thread.Loop); - _socket.Bind(new IPEndPoint(IPAddress.Any, 4001)); - _socket.Listen(10, _connectionCallback, this); + ListenSocket = new UvTcpHandle(); + ListenSocket.Init(Thread.Loop); + ListenSocket.Bind(new IPEndPoint(IPAddress.Any, 4001)); + ListenSocket.Listen(10, _connectionCallback, this); tcs.SetResult(0); } catch (Exception ex) @@ -51,33 +73,25 @@ namespace Microsoft.AspNet.Server.Kestrel } } - private void OnConnection(UvStreamHandle socket, int status) + private void OnConnection(UvStreamHandle listenSocket, int status) { - var connection = new UvTcpHandle(); - connection.Init(_thread.Loop); - socket.Accept(connection); - connection.ReadStart(OnRead, null); - } + var acceptSocket = new UvTcpHandle(); + acceptSocket.Init(Thread.Loop); + listenSocket.Accept(acceptSocket); - private void OnRead(UvStreamHandle socket, int count, byte[] data, object _) - { - var text = Encoding.UTF8.GetString(data); - if (count <= 0) - { - socket.Close(); - } + var connection = new Connection(this, acceptSocket); + connection.Start(); } public void Dispose() { - var socket = _socket; - _socket = null; - _thread.Post(OnDispose, socket); + Thread.Post(OnDispose, ListenSocket); + ListenSocket = null; } - private void OnDispose(object socket) + private void OnDispose(object listenSocket) { - ((UvHandle)socket).Close(); + ((UvHandle)listenSocket).Close(); } } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPool.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPool.cs new file mode 100644 index 0000000000..f97675f776 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPool.cs @@ -0,0 +1,154 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Net.Sockets; + +namespace Microsoft.AspNet.Server.Kestrel.Http +{ + public interface IMemoryPool + { + byte[] Empty { get; } + + byte[] AllocByte(int minimumSize); + void FreeByte(byte[] memory); + + char[] AllocChar(int minimumSize); + void FreeChar(char[] memory); + + /// + /// Acquires a sub-segment of a larger memory allocation. Used for async sends of write-behind + /// buffers to reduce number of array segments pinned + /// + /// The smallest length of the ArraySegment.Count that may be returned + /// An array segment which is a sub-block of a larger allocation + ArraySegment AllocSegment(int minimumSize); + + /// + /// Frees a sub-segment of a larger memory allocation produced by AllocSegment. The original ArraySegment + /// must be frees exactly once and must have the same offset and count that was returned by the Alloc. + /// If a segment is not freed it won't be re-used and has the same effect as a memory leak, so callers must be + /// implemented exactly correctly. + /// + /// The sub-block that was originally returned by a call to AllocSegment. + void FreeSegment(ArraySegment segment); + } + + public class MemoryPool : IMemoryPool + { + static readonly byte[] EmptyArray = new byte[0]; + + class Pool + { + readonly Stack _stack = new Stack(); + readonly object _sync = new object(); + + public T[] Alloc(int size) + { + lock (_sync) + { + if (_stack.Count != 0) + { + return _stack.Pop(); + } + } + return new T[size]; + } + + public void Free(T[] value, int limit) + { + lock (_sync) + { + if (_stack.Count < limit) + { + _stack.Push(value); + } + } + } + } + + readonly Pool _pool1 = new Pool(); + readonly Pool _pool2 = new Pool(); + readonly Pool _pool3 = new Pool(); + + public byte[] Empty + { + get + { + return EmptyArray; + } + } + + public byte[] AllocByte(int minimumSize) + { + if (minimumSize == 0) + { + return EmptyArray; + } + if (minimumSize <= 1024) + { + return _pool1.Alloc(1024); + } + if (minimumSize <= 2048) + { + return _pool2.Alloc(2048); + } + return new byte[minimumSize]; + } + + public void FreeByte(byte[] memory) + { + if (memory == null) + { + return; + } + switch (memory.Length) + { + case 1024: + _pool1.Free(memory, 256); + break; + case 2048: + _pool2.Free(memory, 64); + break; + } + } + + public char[] AllocChar(int minimumSize) + { + if (minimumSize == 0) + { + return new char[0]; + } + if (minimumSize <= 128) + { + return _pool3.Alloc(128); + } + return new char[minimumSize]; + } + + public void FreeChar(char[] memory) + { + if (memory == null) + { + return; + } + switch (memory.Length) + { + case 128: + _pool3.Free(memory, 256); + break; + } + } + + public ArraySegment AllocSegment(int minimumSize) + { + return new ArraySegment(AllocByte(minimumSize)); + } + + public void FreeSegment(ArraySegment segment) + { + FreeByte(segment.Array); + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolTextWriter.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolTextWriter.cs new file mode 100644 index 0000000000..c094178846 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolTextWriter.cs @@ -0,0 +1,155 @@ +// 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. + +using System; +using System.IO; +using System.Text; + +namespace Microsoft.AspNet.Server.Kestrel.Http +{ + public class MemoryPoolTextWriter : TextWriter + { + private readonly IMemoryPool _memory; + + private char[] _textArray; + private int _textBegin; + private int _textEnd; + // ReSharper disable InconsistentNaming + private const int _textLength = 128; + // ReSharper restore InconsistentNaming + + private byte[] _dataArray; + private int _dataEnd; + + private readonly Encoder _encoder; + + public ArraySegment Buffer + { + get + { + return new ArraySegment(_dataArray, 0, _dataEnd); + } + } + + public MemoryPoolTextWriter(IMemoryPool memory) + { + _memory = memory; + _textArray = _memory.AllocChar(_textLength); + _dataArray = _memory.Empty; + _encoder = Encoding.Default.GetEncoder(); + } + + public override Encoding Encoding + { + get + { + return Encoding.Default; + } + } + + protected override void Dispose(bool disposing) + { + try + { + if (disposing) + { + if (_textArray != null) + { + _memory.FreeChar(_textArray); + _textArray = null; + } + if (_dataArray != null) + { + _memory.FreeByte(_dataArray); + _dataArray = null; + } + } + } + finally + { + base.Dispose(disposing); + } + } + + private void Encode(bool flush) + { + var bytesNeeded = _encoder.GetByteCount( + _textArray, + _textBegin, + _textEnd - _textBegin, + flush); + + Grow(bytesNeeded); + + var bytesUsed = _encoder.GetBytes( + _textArray, + _textBegin, + _textEnd - _textBegin, + _dataArray, + _dataEnd, + flush); + + _textBegin = _textEnd = 0; + _dataEnd += bytesUsed; + } + + private void Grow(int minimumAvailable) + { + if (_dataArray.Length - _dataEnd >= minimumAvailable) + { + return; + } + + var newLength = _dataArray.Length + Math.Max(_dataArray.Length, minimumAvailable); + var newArray = _memory.AllocByte(newLength); + Array.Copy(_dataArray, 0, newArray, 0, _dataEnd); + _memory.FreeByte(_dataArray); + _dataArray = newArray; + } + + public override void Write(char value) + { + if (_textLength == _textEnd) + { + Encode(false); + if (_textLength == _textEnd) + { + throw new InvalidOperationException("Unexplainable failure to encode text"); + } + } + + _textArray[_textEnd++] = value; + } + + public override void Write(string value) + { + var sourceIndex = 0; + var sourceLength = value.Length; + while (sourceIndex < sourceLength) + { + if (_textLength == _textEnd) + { + Encode(false); + } + + var count = sourceLength - sourceIndex; + if (count > _textLength - _textEnd) + { + count = _textLength - _textEnd; + } + + value.CopyTo(sourceIndex, _textArray, _textEnd, count); + sourceIndex += count; + _textEnd += count; + } + } + + public override void Flush() + { + while (_textBegin != _textEnd) + { + Encode(true); + } + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBody.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBody.cs new file mode 100644 index 0000000000..0ade9006fb --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBody.cs @@ -0,0 +1,342 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Threading; + +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 = () => { }; + + public bool RequestKeepAlive { get; protected set; } + + protected MessageBody(ConnectionContext context) : base(context) + { + } + + public void Intake(int count) + { + Transfer(count, false); + } + + public void IntakeFin(int count) + { + Transfer(count, true); + if (_continuation != null) + { + _continuation.Invoke(); + } + } + + public abstract void Consume(); + + public static MessageBody For( + string httpVersion, + IDictionary headers, + ConnectionContext context) + { + // see also http://tools.ietf.org/html/rfc2616#section-4.4 + + var keepAlive = httpVersion != "HTTP/1.0"; + + string connection; + if (TryGet(headers, "Connection", out connection)) + { + keepAlive = connection.Equals("keep-alive", StringComparison.OrdinalIgnoreCase); + } + + string transferEncoding; + if (TryGet(headers, "Transfer-Encoding", out transferEncoding)) + { + return new ForChunkedEncoding(keepAlive, context); + } + + string contentLength; + if (TryGet(headers, "Content-Length", out contentLength)) + { + return new ForContentLength(keepAlive, int.Parse(contentLength), context); + } + + if (keepAlive) + { + return new ForContentLength(true, 0, context); + } + + return new ForRemainingData(context); + } + + public static bool TryGet(IDictionary headers, string name, out string value) + { + string[] values; + if (!headers.TryGetValue(name, out values) || values == null) + { + value = null; + return false; + } + var count = values.Length; + if (count == 0) + { + value = null; + return false; + } + if (count == 1) + { + value = values[0]; + return true; + } + value = String.Join(",", values); + return true; + } + + public void Drain(Action continuation) + { + _continuation = continuation; + _continuation.Invoke(); + } + + + class ForRemainingData : MessageBody + { + public ForRemainingData(ConnectionContext context) + : base(context) + { + } + + public override void Consume() + { + var input = _context.SocketInput; + + if (input.RemoteIntakeFin) + { + IntakeFin(input.Buffer.Count); + } + else + { + Intake(input.Buffer.Count); + } + } + } + + class ForContentLength : MessageBody + { + private readonly int _contentLength; + private int _neededLength; + + public ForContentLength(bool keepAlive, int contentLength, ConnectionContext context) + : base(context) + { + RequestKeepAlive = keepAlive; + _contentLength = contentLength; + _neededLength = _contentLength; + } + + public override void Consume() + { + var input = _context.SocketInput; + var consumeLength = Math.Min(_neededLength, input.Buffer.Count); + _neededLength -= consumeLength; + + var consumed = input.Take(consumeLength); + + if (_neededLength != 0) + { + Intake(consumeLength); + } + else + { + IntakeFin(consumeLength); + } + } + } + + + /// + /// http://tools.ietf.org/html/rfc2616#section-3.6.1 + /// + class ForChunkedEncoding : MessageBody + { + private int _neededLength; + + private Mode _mode = Mode.ChunkSizeLine; + + private enum Mode + { + ChunkSizeLine, + ChunkData, + ChunkDataCRLF, + Complete, + }; + + + public ForChunkedEncoding(bool keepAlive, ConnectionContext context) + : base(context) + { + RequestKeepAlive = keepAlive; + } + + public override void Consume() + { + var input = _context.SocketInput; + for (; ;) + { + switch (_mode) + { + case Mode.ChunkSizeLine: + var chunkSize = 0; + if (!TakeChunkedLine(input, ref chunkSize)) + { + return; + } + + _neededLength = chunkSize; + if (chunkSize == 0) + { + _mode = Mode.Complete; + IntakeFin(0); + return; + } + _mode = Mode.ChunkData; + break; + + case Mode.ChunkData: + if (_neededLength == 0) + { + _mode = Mode.ChunkDataCRLF; + break; + } + if (input.Buffer.Count == 0) + { + return; + } + + var consumeLength = Math.Min(_neededLength, input.Buffer.Count); + _neededLength -= consumeLength; + + Intake(consumeLength); + break; + + case Mode.ChunkDataCRLF: + if (input.Buffer.Count < 2) + { + return; + } + var crlf = input.Take(2); + if (crlf.Array[crlf.Offset] != '\r' || + crlf.Array[crlf.Offset + 1] != '\n') + { + throw new NotImplementedException("INVALID REQUEST FORMAT"); + } + _mode = Mode.ChunkSizeLine; + break; + + default: + throw new NotImplementedException("INVALID REQUEST FORMAT"); + } + } + } + + private static bool TakeChunkedLine(SocketInput baton, ref int chunkSizeOut) + { + var remaining = baton.Buffer; + if (remaining.Count < 2) + { + return false; + } + var ch0 = remaining.Array[remaining.Offset]; + var chunkSize = 0; + var mode = 0; + for (var index = 0; index != remaining.Count - 1; ++index) + { + var ch1 = remaining.Array[remaining.Offset + index + 1]; + + if (mode == 0) + { + if (ch0 >= '0' && ch0 <= '9') + { + chunkSize = chunkSize * 0x10 + (ch0 - '0'); + } + else if (ch0 >= 'A' && ch0 <= 'F') + { + chunkSize = chunkSize * 0x10 + (ch0 - ('A' - 10)); + } + else if (ch0 >= 'a' && ch0 <= 'f') + { + chunkSize = chunkSize * 0x10 + (ch0 - ('a' - 10)); + } + else + { + throw new NotImplementedException("INVALID REQUEST FORMAT"); + } + mode = 1; + } + else if (mode == 1) + { + if (ch0 >= '0' && ch0 <= '9') + { + chunkSize = chunkSize * 0x10 + (ch0 - '0'); + } + else if (ch0 >= 'A' && ch0 <= 'F') + { + chunkSize = chunkSize * 0x10 + (ch0 - ('A' - 10)); + } + else if (ch0 >= 'a' && ch0 <= 'f') + { + chunkSize = chunkSize * 0x10 + (ch0 - ('a' - 10)); + } + else if (ch0 == ';') + { + mode = 2; + } + else if (ch0 == '\r' && ch1 == '\n') + { + baton.Skip(index + 2); + chunkSizeOut = chunkSize; + return true; + } + else + { + throw new NotImplementedException("INVALID REQUEST FORMAT"); + } + } + else if (mode == 2) + { + if (ch0 == '\r' && ch1 == '\n') + { + baton.Skip(index + 2); + chunkSizeOut = chunkSize; + return true; + } + else + { + // chunk-extensions not currently parsed + } + } + + ch0 = ch1; + } + return false; + } + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBodyExchanger.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBodyExchanger.cs new file mode 100644 index 0000000000..29837ad131 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBodyExchanger.cs @@ -0,0 +1,144 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Server.Kestrel.Http +{ + /// + /// Summary description for MessageBodyExchanger + /// + public class MessageBodyExchanger + { + private static readonly WaitCallback _completePending = CompletePending; + protected readonly ConnectionContext _context; + + object _sync = new Object(); + + ArraySegment _buffer; + Queue _reads = new Queue(); + + public MessageBodyExchanger(ConnectionContext context) + { + _context = context; + _buffer = new ArraySegment(_context.Memory.Empty); + } + + public bool LocalIntakeFin { get; set; } + + public void Transfer(int count, bool fin) + { + var input = _context.SocketInput; + lock (_sync) + { + // NOTE: this should not copy each time + var oldBuffer = _buffer; + var newData = _context.SocketInput.Take(count); + + var newBuffer = new ArraySegment( + _context.Memory.AllocByte(oldBuffer.Count + newData.Count), + 0, + oldBuffer.Count + newData.Count); + + Array.Copy(oldBuffer.Array, oldBuffer.Offset, newBuffer.Array, newBuffer.Offset, oldBuffer.Count); + Array.Copy(newData.Array, newData.Offset, newBuffer.Array, newBuffer.Offset + oldBuffer.Count, newData.Count); + + _buffer = newBuffer; + _context.Memory.FreeByte(oldBuffer.Array); + + if (fin) + { + LocalIntakeFin = true; + } + if (_reads.Any()) + { + ThreadPool.QueueUserWorkItem(_completePending, this); + } + } + } + + public Task ReadAsync(ArraySegment buffer) + { + for (; ;) + { + while (CompletePending()) + { + // earlier reads have priority + } + lock (_sync) + { + if (_buffer.Count != 0 || buffer.Count == 0 || LocalIntakeFin) + { + // there is data we can take right now + if (_reads.Any()) + { + // someone snuck in, try again + continue; + } + + var count = Math.Min(buffer.Count, _buffer.Count); + Array.Copy(_buffer.Array, _buffer.Offset, buffer.Array, buffer.Offset, count); + _buffer = new ArraySegment(_buffer.Array, _buffer.Offset + count, _buffer.Count - count); + return Task.FromResult(count); + } + else + { + // add ourselves to the line + var tcs = new TaskCompletionSource(); + _reads.Enqueue(new ReadOperation + { + Buffer = buffer, + CompletionSource = tcs, + }); + return tcs.Task; + } + } + } + } + + static void CompletePending(object state) + { + while (((MessageBodyExchanger)state).CompletePending()) + { + // loop until none left + } + } + + bool CompletePending() + { + ReadOperation read; + int count; + lock (_sync) + { + if (_buffer.Count == 0 && !LocalIntakeFin) + { + return false; + } + if (!_reads.Any()) + { + return false; + } + read = _reads.Dequeue(); + + count = Math.Min(read.Buffer.Count, _buffer.Count); + Array.Copy(_buffer.Array, _buffer.Offset, read.Buffer.Array, read.Buffer.Offset, count); + _buffer = new ArraySegment(_buffer.Array, _buffer.Offset + count, _buffer.Count - count); + } + if (read.CompletionSource != null) + { + read.CompletionSource.SetResult(count); + } + return true; + } + + public struct ReadOperation + { + public TaskCompletionSource CompletionSource; + public ArraySegment Buffer; + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/ReasonPhrases.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/ReasonPhrases.cs new file mode 100644 index 0000000000..33076d1505 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/ReasonPhrases.cs @@ -0,0 +1,133 @@ +// 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. + +using System.Globalization; + +namespace Microsoft.AspNet.Server.Kestrel.Http +{ + public static class ReasonPhrases + { + public static string ToStatus(int statusCode, string reasonPhrase = null) + { + if (string.IsNullOrEmpty(reasonPhrase)) + { + reasonPhrase = ToReasonPhrase(statusCode); + } + return statusCode.ToString(CultureInfo.InvariantCulture) + " " + reasonPhrase; + } + + public static string ToReasonPhrase(int statusCode) + { + switch (statusCode) + { + case 100: + return "Continue"; + case 101: + return "Switching Protocols"; + case 102: + return "Processing"; + case 200: + return "OK"; + case 201: + return "Created"; + case 202: + return "Accepted"; + case 203: + return "Non-Authoritative Information"; + case 204: + return "No Content"; + case 205: + return "Reset Content"; + case 206: + return "Partial Content"; + case 207: + return "Multi-Status"; + case 226: + return "IM Used"; + case 300: + return "Multiple Choices"; + case 301: + return "Moved Permanently"; + case 302: + return "Found"; + case 303: + return "See Other"; + case 304: + return "Not Modified"; + case 305: + return "Use Proxy"; + case 306: + return "Reserved"; + case 307: + return "Temporary Redirect"; + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 402: + return "Payment Required"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + case 406: + return "Not Acceptable"; + case 407: + return "Proxy Authentication Required"; + case 408: + return "Request Timeout"; + case 409: + return "Conflict"; + case 410: + return "Gone"; + case 411: + return "Length Required"; + case 412: + return "Precondition Failed"; + case 413: + return "Request Entity Too Large"; + case 414: + return "Request-URI Too Long"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Requested Range Not Satisfiable"; + case 417: + return "Expectation Failed"; + case 418: + return "I'm a Teapot"; + case 422: + return "Unprocessable Entity"; + case 423: + return "Locked"; + case 424: + return "Failed Dependency"; + case 426: + return "Upgrade Required"; + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Timeout"; + case 505: + return "HTTP Version Not Supported"; + case 506: + return "Variant Also Negotiates"; + case 507: + return "Insufficient Storage"; + case 510: + return "Not Extended"; + default: + return null; + } + } + } + +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/SocketInput.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketInput.cs new file mode 100644 index 0000000000..5016a9881f --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketInput.cs @@ -0,0 +1,106 @@ +// 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. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNet.Server.Kestrel.Http +{ + public class SocketInput + { + private readonly IMemoryPool _memory; + private GCHandle _gcHandle; + + public SocketInput(IMemoryPool memory) + { + _memory = memory; + Buffer = new ArraySegment(_memory.Empty, 0, 0); + } + + public ArraySegment Buffer { get; set; } + + public bool RemoteIntakeFin { get; set; } + + + public void Skip(int count) + { + Buffer = new ArraySegment(Buffer.Array, Buffer.Offset + count, Buffer.Count - count); + } + + public ArraySegment Take(int count) + { + var taken = new ArraySegment(Buffer.Array, Buffer.Offset, count); + Skip(count); + return taken; + } + + public void Free() + { + if (Buffer.Count == 0 && Buffer.Array.Length != 0) + { + _memory.FreeByte(Buffer.Array); + Buffer = new ArraySegment(_memory.Empty, 0, 0); + } + } + + public ArraySegment Available(int minimumSize) + { + if (Buffer.Count == 0 && Buffer.Offset != 0) + { + Buffer = new ArraySegment(Buffer.Array, 0, 0); + } + + var availableSize = Buffer.Array.Length - Buffer.Offset - Buffer.Count; + + if (availableSize < minimumSize) + { + if (availableSize + Buffer.Offset >= minimumSize) + { + Array.Copy(Buffer.Array, Buffer.Offset, Buffer.Array, 0, Buffer.Count); + if (Buffer.Count != 0) + { + Buffer = new ArraySegment(Buffer.Array, 0, Buffer.Count); + } + availableSize = Buffer.Array.Length - Buffer.Offset - Buffer.Count; + } + else + { + var largerSize = Buffer.Array.Length + Math.Max(Buffer.Array.Length, minimumSize); + var larger = new ArraySegment(_memory.AllocByte(largerSize), 0, Buffer.Count); + if (Buffer.Count != 0) + { + Array.Copy(Buffer.Array, Buffer.Offset, larger.Array, 0, Buffer.Count); + } + _memory.FreeByte(Buffer.Array); + Buffer = larger; + availableSize = Buffer.Array.Length - Buffer.Offset - Buffer.Count; + } + } + return new ArraySegment(Buffer.Array, Buffer.Offset + Buffer.Count, availableSize); + } + + public void Extend(int count) + { + Debug.Assert(count >= 0); + Debug.Assert(Buffer.Offset >= 0); + Debug.Assert(Buffer.Offset <= Buffer.Array.Length); + Debug.Assert(Buffer.Offset + Buffer.Count <= Buffer.Array.Length); + Debug.Assert(Buffer.Offset + Buffer.Count + count <= Buffer.Array.Length); + + Buffer = new ArraySegment(Buffer.Array, Buffer.Offset, Buffer.Count + count); + } + public IntPtr Pin(int minimumSize) + { + var segment = Available(minimumSize); + _gcHandle = GCHandle.Alloc(segment.Array, GCHandleType.Pinned); + return _gcHandle.AddrOfPinnedObject() + segment.Offset; + } + public void Unpin(int count) + { + _gcHandle.Free(); + Extend(count); + } + + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs new file mode 100644 index 0000000000..d33719c57d --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs @@ -0,0 +1,106 @@ +// 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. + +using Microsoft.AspNet.Server.Kestrel.Networking; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNet.Server.Kestrel.Http +{ + /// + /// Operations performed for buffered socket output + /// + public interface ISocketOutput + { + void Write(ArraySegment buffer, Action callback, object state); + } + + public class SocketOutput : ISocketOutput + { + private readonly KestrelThread _thread; + private readonly UvStreamHandle _socket; + + public SocketOutput(KestrelThread thread, UvStreamHandle socket) + { + _thread = thread; + _socket = socket; + } + + public void Write(ArraySegment buffer, Action callback, object state) + { + var req = new ThisWriteReq(); + req.Init(_thread.Loop); + req.Contextualize(this, _socket, buffer, callback, state); + _thread.Post(x => + { + ((ThisWriteReq)x).Write(); + }, req); + } + + public class ThisWriteReq : UvWriteReq + { + private static readonly Action _writeCallback = WriteCallback; + private static void WriteCallback(UvWriteReq req, int status, object state) + { + ((ThisWriteReq)state).OnWrite(req, status); + } + + SocketOutput _self; + ArraySegment _buffer; + Action _drained; + UvStreamHandle _socket; + Action _callback; + object _state; + GCHandle _pin; + + internal void Contextualize( + SocketOutput socketOutput, + UvStreamHandle socket, + ArraySegment buffer, + Action callback, + object state) + { + _self = socketOutput; + _socket = socket; + _buffer = buffer; + _callback = callback; + _state = state; + } + + public void Write() + { + _pin = GCHandle.Alloc(_buffer.Array, GCHandleType.Pinned); + var buf = new Libuv.uv_buf_t + { + len = (uint)_buffer.Count, + memory = _pin.AddrOfPinnedObject() + _buffer.Offset + }; + + Write( + _socket, + new[] { buf }, + 1, + _writeCallback, + this); + } + + private void OnWrite(UvWriteReq req, int status) + { + _pin.Free(); + //NOTE: pool this? + Close(); + _callback(_state); + } + } + + + public bool Flush(Action drained) + { + return false; + } + + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/Disposable.cs b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/Disposable.cs index 9bb2a7f4bd..8e61adb824 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/Disposable.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/Disposable.cs @@ -1,4 +1,7 @@ -using System; +// 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. + +using System; namespace Microsoft.AspNet.Server.Kestrel { diff --git a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs index 83dd84a4a5..43d09a593c 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNet.Server.Kestrel.Networking; +// 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. + +using Microsoft.AspNet.Server.Kestrel.Networking; using System; using System.Collections.Generic; using System.Threading; diff --git a/src/Microsoft.AspNet.Server.Kestrel/KestrelEngine.cs b/src/Microsoft.AspNet.Server.Kestrel/KestrelEngine.cs index b404d6eb63..add92c5f37 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/KestrelEngine.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/KestrelEngine.cs @@ -1,9 +1,12 @@ +// 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. using System; using Microsoft.AspNet.Server.Kestrel.Networking; using System.Threading; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.AspNet.Server.Kestrel.Http; namespace Microsoft.AspNet.Server.Kestrel { @@ -14,11 +17,13 @@ namespace Microsoft.AspNet.Server.Kestrel { Threads = new List(); Listeners = new List(); + Memory = new MemoryPool(); Libuv = new Libuv(); Libuv.Load("libuv.dll"); } public Libuv Libuv { get; private set; } + public IMemoryPool Memory { get; set; } public List Threads { get; private set; } public List Listeners { get; private set; } @@ -44,13 +49,13 @@ namespace Microsoft.AspNet.Server.Kestrel Threads.Clear(); } - public IDisposable CreateServer() + public IDisposable CreateServer(Func app) { var listeners = new List(); foreach (var thread in Threads) { - var listener = new Listener(thread); - listener.StartAsync().Wait(); + var listener = new Listener(Memory); + listener.StartAsync(thread, app).Wait(); listeners.Add(listener); } return new Disposable(() => diff --git a/src/Microsoft.AspNet.Server.Kestrel/Microsoft.AspNet.Server.Kestrel.kproj b/src/Microsoft.AspNet.Server.Kestrel/Microsoft.AspNet.Server.Kestrel.kproj new file mode 100644 index 0000000000..ca3a199436 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Microsoft.AspNet.Server.Kestrel.kproj @@ -0,0 +1,57 @@ + + + + 12.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + f510611a-3bee-4b88-a613-5f4a74ed82a1 + Library + + + ConsoleDebugger + + + WebDebugger + + + + + + + 2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs index 17a2299a44..a9e5dd7676 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs @@ -177,6 +177,50 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking Check(_uv_read_stop(handle)); } + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_try_write(UvStreamHandle handle, Libuv.uv_buf_t[] bufs, int nbufs); + uv_try_write _uv_try_write; + public int try_write(UvStreamHandle handle, Libuv.uv_buf_t[] bufs, int nbufs) + { + return Check(_uv_try_write(handle, bufs, nbufs)); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_write_cb(IntPtr req, int status); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_write(UvWriteReq req, UvStreamHandle handle, Libuv.uv_buf_t[] bufs, int nbufs, uv_write_cb cb); + uv_write _uv_write; + public void write(UvWriteReq req, UvStreamHandle handle, Libuv.uv_buf_t[] bufs, int nbufs, uv_write_cb cb) + { + Check(_uv_write(req, handle, bufs, nbufs, cb)); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_shutdown_cb(IntPtr req, int status); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_shutdown(UvShutdownReq req, UvStreamHandle handle, uv_shutdown_cb cb); + uv_shutdown _uv_shutdown; + public void shutdown(UvShutdownReq req, UvStreamHandle handle, uv_shutdown_cb cb) + { + Check(_uv_shutdown(req, handle, cb)); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_handle_size(int handleType); + uv_handle_size _uv_handle_size; + public int handle_size(int handleType) + { + return _uv_handle_size(handleType); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_req_size(int handleType); + uv_req_size _uv_req_size; + public int req_size(int handleType) + { + return _uv_req_size(handleType); + } + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] delegate int uv_ip4_addr(string ip, int port, out sockaddr addr); @@ -209,5 +253,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking public IntPtr memory; } + //int handle_size_async; + //int handle_size_tcp; + //int req_size_write; + //int req_size_shutdown; } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs index 033c649484..e76dac9176 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs @@ -2,38 +2,15 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Runtime.InteropServices; namespace Microsoft.AspNet.Server.Kestrel.Networking { - public abstract class UvHandle : SafeHandle + public abstract class UvHandle : UvMemory { - protected Libuv _uv; static Libuv.uv_close_cb _close_cb = DestroyHandle; - public UvHandle() : base(IntPtr.Zero, true) - { - } - public override bool IsInvalid - { - get - { - return handle == IntPtr.Zero; - } - } - unsafe protected void CreateHandle(Libuv uv, int size) - { - _uv = uv; - handle = Marshal.AllocCoTaskMem(size); - *(IntPtr*)handle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); - } - - protected void CreateHandle(UvLoopHandle loop, int size) - { - CreateHandle(loop._uv, size); - } protected override bool ReleaseHandle() { var memory = handle; @@ -45,22 +22,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking return true; } - unsafe protected static void DestroyHandle(IntPtr memory) - { - var gcHandlePtr = *(IntPtr*)memory; - if (gcHandlePtr != IntPtr.Zero) - { - GCHandle.FromIntPtr(gcHandlePtr).Free(); - } - Marshal.FreeCoTaskMem(memory); - } - - unsafe public static THandle FromIntPtr(IntPtr handle) - { - GCHandle gcHandle = GCHandle.FromIntPtr(*(IntPtr*)handle); - return (THandle)gcHandle.Target; - } - public void Reference() { _uv.@ref(this); diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopHandle.cs index 73bab4b5f7..ec87e7db56 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopHandle.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopHandle.cs @@ -13,7 +13,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking _uv.loop_init(this); } - public int Run(int mode = 0) { return _uv.run(this, mode); diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemory.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemory.cs new file mode 100644 index 0000000000..145b286fe1 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemory.cs @@ -0,0 +1,58 @@ +// 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. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + /// + /// Summary description for UvMemory + /// + public abstract class UvMemory : SafeHandle + { + protected Libuv _uv; + public UvMemory() : base(IntPtr.Zero, true) + { + } + + public Libuv Libuv { get { return _uv; } } + + public override bool IsInvalid + { + get + { + return handle == IntPtr.Zero; + } + } + + unsafe protected void CreateHandle(Libuv uv, int size) + { + _uv = uv; + handle = Marshal.AllocCoTaskMem(size); + *(IntPtr*)handle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); + } + + protected void CreateHandle(UvLoopHandle loop, int size) + { + CreateHandle(loop._uv, size); + } + + unsafe protected static void DestroyHandle(IntPtr memory) + { + var gcHandlePtr = *(IntPtr*)memory; + if (gcHandlePtr != IntPtr.Zero) + { + GCHandle.FromIntPtr(gcHandlePtr).Free(); + } + Marshal.FreeCoTaskMem(memory); + } + + unsafe public static THandle FromIntPtr(IntPtr handle) + { + GCHandle gcHandle = GCHandle.FromIntPtr(*(IntPtr*)handle); + return (THandle)gcHandle.Target; + } + + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvShutdownReq.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvShutdownReq.cs new file mode 100644 index 0000000000..2bc23468e2 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvShutdownReq.cs @@ -0,0 +1,38 @@ +// 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. + +using System; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + /// + /// Summary description for UvShutdownRequest + /// + public class UvShutdownReq : UvReq + { + private readonly static Libuv.uv_shutdown_cb _uv_shutdown_cb = UvShutdownCb; + + Action _callback; + object _state; + + public void Init(UvLoopHandle loop) + { + CreateHandle(loop.Libuv, loop.Libuv.req_size(3)); + } + + public void Shutdown(UvStreamHandle handle, Action callback, object state) + { + _callback = callback; + _state = state; + _uv.shutdown(this, handle, _uv_shutdown_cb); + } + + private static void UvShutdownCb(IntPtr ptr, int status) + { + var req = FromIntPtr(ptr); + req._callback(req, status, req._state); + req._callback = null; + req._state = null; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvStreamHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvStreamHandle.cs index e4d33aa849..7f2ff901e7 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvStreamHandle.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvStreamHandle.cs @@ -15,12 +15,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking public Action _connectionCallback; public object _connectionState; - public Action _readCallback; + public Func _allocCallback; + + public Action _readCallback; public object _readState; - public UvStreamHandle() - { - } public void Listen(int backlog, Action callback, object state) { @@ -35,10 +34,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking } public void ReadStart( - Action callback, + Func allocCallback, + Action readCallback, object state) { - _readCallback = callback; + _allocCallback = allocCallback; + _readCallback = readCallback; _readState = state; _uv.read_start(this, _uv_alloc_cb, _uv_read_cb); } @@ -48,38 +49,38 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking _uv.read_stop(this); } + public int TryWrite(Libuv.uv_buf_t buf) + { + return _uv.try_write(this, new[] { buf }, 1); + } + + private static void UvConnectionCb(IntPtr handle, int status) { var stream = FromIntPtr(handle); stream._connectionCallback(stream, status, stream._connectionState); } - private static void UvAllocCb(IntPtr server, int suggested_size, out Libuv.uv_buf_t buf) + private static void UvAllocCb(IntPtr handle, int suggested_size, out Libuv.uv_buf_t buf) { - buf = new Libuv.uv_buf_t - { - memory = Marshal.AllocCoTaskMem(suggested_size), - len = (uint)suggested_size, - }; - + var stream = FromIntPtr(handle); + buf = stream._allocCallback(stream, suggested_size, stream._readState); } - private static void UvReadCb(IntPtr ptr, int nread, ref Libuv.uv_buf_t buf) + private static void UvReadCb(IntPtr handle, int nread, ref Libuv.uv_buf_t buf) { - var stream = FromIntPtr(ptr); + var stream = FromIntPtr(handle); + if (nread == -4095) { - stream._readCallback(stream, 0, null, stream._readState); - Marshal.FreeCoTaskMem(buf.memory); + stream._readCallback(stream, 0, stream._readState); return; } var length = stream._uv.Check(nread); - var data = new byte[length]; - Marshal.Copy(buf.memory, data, 0, length); - Marshal.FreeCoTaskMem(buf.memory); - stream._readCallback(stream, length, data, stream._readState); + stream._readCallback(stream, nread, stream._readState); } + } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteRequest.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteRequest.cs new file mode 100644 index 0000000000..17381696c5 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteRequest.cs @@ -0,0 +1,57 @@ +// 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. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + /// + /// Summary description for UvWriteRequest + /// + public class UvWriteReq : UvReq + { + private readonly static Libuv.uv_write_cb _uv_write_cb = UvWriteCb; + + Action _callback; + object _state; + + public void Init(UvLoopHandle loop) + { + CreateHandle(loop.Libuv, loop.Libuv.req_size(2)); + } + + public void Write(UvStreamHandle handle, Libuv.uv_buf_t[] bufs, int nbufs, Action callback, object state) + { + _callback = callback; + _state = state; + _uv.write(this, handle, bufs, nbufs, _uv_write_cb); + } + + private static void UvWriteCb(IntPtr ptr, int status) + { + var req = FromIntPtr(ptr); + req._callback(req, status, req._state); + req._callback = null; + req._state = null; + } + } + + 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); + handle = IntPtr.Zero; + return true; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Project.json b/src/Microsoft.AspNet.Server.Kestrel/Project.json index 0802ababfa..cb7ece47c7 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Project.json +++ b/src/Microsoft.AspNet.Server.Kestrel/Project.json @@ -1,6 +1,7 @@ { "version": "0.1-alpha-*", "dependencies": { + "Microsoft.AspNet.Hosting": "0.1-*" }, "compilationOptions": { "allowUnsafe": true diff --git a/src/SampleApp/Program.cs b/src/SampleApp/Program.cs new file mode 100644 index 0000000000..446628b570 --- /dev/null +++ b/src/SampleApp/Program.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading.Tasks; + +namespace SampleApp +{ + public class Program + { + public static void Main(string[] args) + { + var engine = new Microsoft.AspNet.Server.Kestrel.KestrelEngine(); + engine.Start(1); + using (var server = engine.CreateServer(App)) + { + Console.WriteLine("Hello World"); + Console.ReadLine(); + } + engine.Stop(); + } + + private static async Task App(object arg) + { + var httpContext = new Microsoft.AspNet.PipelineCore.DefaultHttpContext( + new Microsoft.AspNet.FeatureModel.FeatureCollection( + new Microsoft.AspNet.FeatureModel.FeatureObject(arg))); + + Console.WriteLine("{0} {1}{2}{3}", + httpContext.Request.Method, + httpContext.Request.PathBase, + httpContext.Request.Path, + httpContext.Request.QueryString); + + httpContext.Response.ContentType = "text/plain"; + await httpContext.Response.WriteAsync("Hello world"); + } + } +} diff --git a/src/SampleApp/SampleApp.kproj b/src/SampleApp/SampleApp.kproj new file mode 100644 index 0000000000..7452dd8c38 --- /dev/null +++ b/src/SampleApp/SampleApp.kproj @@ -0,0 +1,35 @@ + + + + 12.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + Debug + AnyCPU + + + + 2c3cb3dc-eebf-4f52-9e1c-4f2f972e76c3 + Console + + + ConsoleDebugger + + + WebDebugger + + + + + 2.0 + + + + + + + + + + \ No newline at end of file diff --git a/src/SampleApp/libuv.dll b/src/SampleApp/libuv.dll new file mode 100644 index 0000000000000000000000000000000000000000..d3a583471805a26c08d10de032d0efb67350ed3e GIT binary patch literal 317440 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

4C+ejN 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 public class EngineTests { - [Fact] + private async Task App(object callContext) + { + var request = callContext as IHttpRequestFeature; + var response = callContext as IHttpResponseFeature; + for (; ;) + { + var buffer = new byte[8192]; + var count = await request.Body.ReadAsync(buffer, 0, buffer.Length); + if (count == 0) + { + break; + } + await response.Body.WriteAsync(buffer, 0, count); + } + } + + [Fact] public async Task EngineCanStartAndStop() { var engine = new KestrelEngine(); engine.Start(1); engine.Stop(); } + [Fact] public async Task ListenerCanCreateAndDispose() { var engine = new KestrelEngine(); engine.Start(1); - var started = engine.CreateServer(); + var started = engine.CreateServer(App); + started.Dispose(); + engine.Stop(); + } + + + [Fact] + public async Task ConnectionCanReadAndWrite() + { + var engine = new KestrelEngine(); + engine.Start(1); + var started = engine.CreateServer(App); + + var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + socket.Connect(new IPEndPoint(IPAddress.Loopback, 4001)); + 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.Stop(); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNet.Server.KestralTests/MessageBodyExchangerTests.cs b/test/Microsoft.AspNet.Server.KestralTests/MessageBodyExchangerTests.cs new file mode 100644 index 0000000000..db8ff8218f --- /dev/null +++ b/test/Microsoft.AspNet.Server.KestralTests/MessageBodyExchangerTests.cs @@ -0,0 +1,190 @@ +using Microsoft.AspNet.Server.Kestrel; +using Microsoft.AspNet.Server.Kestrel.Http; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNet.Server.KestralTests +{ + /// + /// Summary description for MessageBodyExchangerTests + /// + public class MessageBodyExchangerTests + { + [Fact] + public async Task CallingReadAsyncBeforeTransfer() + { + var testInput = new TestInput(); + var context = new ConnectionContext(); + context.SocketInput = new SocketInput(new MemoryPool()); + + var exchanger = new MessageBodyExchanger(testInput.ConnectionContext); + + var buffer1 = new byte[1024]; + var buffer2 = new byte[1024]; + var task1 = exchanger.ReadAsync(new ArraySegment(buffer1)); + var task2 = exchanger.ReadAsync(new ArraySegment(buffer2)); + + Assert.False(task1.IsCompleted); + Assert.False(task2.IsCompleted); + + testInput.Add("Hello"); + + exchanger.Transfer(3, false); + + var count1 = await task1; + + Assert.True(task1.IsCompleted); + Assert.False(task2.IsCompleted); + AssertASCII("Hel", new ArraySegment(buffer1, 0, count1)); + + exchanger.Transfer(2, false); + + var count2 = await task2; + + Assert.True(task1.IsCompleted); + Assert.True(task2.IsCompleted); + AssertASCII("lo", new ArraySegment(buffer2, 0, count2)); + } + + [Fact] + public async Task CallingTransferBeforeReadAsync() + { + var testInput = new TestInput(); + var context = new ConnectionContext(); + context.SocketInput = new SocketInput(new MemoryPool()); + + var exchanger = new MessageBodyExchanger(testInput.ConnectionContext); + + testInput.Add("Hello"); + + exchanger.Transfer(5, false); + + var buffer1 = new byte[1024]; + var buffer2 = new byte[1024]; + var task1 = exchanger.ReadAsync(new ArraySegment(buffer1)); + var task2 = exchanger.ReadAsync(new ArraySegment(buffer2)); + + Assert.True(task1.IsCompleted); + Assert.False(task2.IsCompleted); + + await task1; + + var count1 = await task1; + + Assert.True(task1.IsCompleted); + Assert.False(task2.IsCompleted); + AssertASCII("Hello", new ArraySegment(buffer1, 0, count1)); + } + + [Fact] + public async Task TransferZeroBytesDoesNotReleaseReadAsync() + { + var testInput = new TestInput(); + var context = new ConnectionContext(); + context.SocketInput = new SocketInput(new MemoryPool()); + + var exchanger = new MessageBodyExchanger(testInput.ConnectionContext); + + var buffer1 = new byte[1024]; + var buffer2 = new byte[1024]; + var task1 = exchanger.ReadAsync(new ArraySegment(buffer1)); + var task2 = exchanger.ReadAsync(new ArraySegment(buffer2)); + + Assert.False(task1.IsCompleted); + Assert.False(task2.IsCompleted); + + testInput.Add("Hello"); + + exchanger.Transfer(3, false); + + var count1 = await task1; + + Assert.True(task1.IsCompleted); + Assert.False(task2.IsCompleted); + AssertASCII("Hel", new ArraySegment(buffer1, 0, count1)); + + exchanger.Transfer(0, false); + + Assert.True(task1.IsCompleted); + Assert.False(task2.IsCompleted); + } + + [Fact] + public async Task TransferFinDoesReleaseReadAsync() + { + var testInput = new TestInput(); + var context = new ConnectionContext(); + context.SocketInput = new SocketInput(new MemoryPool()); + + var exchanger = new MessageBodyExchanger(testInput.ConnectionContext); + + var buffer1 = new byte[1024]; + var buffer2 = new byte[1024]; + var task1 = exchanger.ReadAsync(new ArraySegment(buffer1)); + var task2 = exchanger.ReadAsync(new ArraySegment(buffer2)); + + Assert.False(task1.IsCompleted); + Assert.False(task2.IsCompleted); + + testInput.Add("Hello"); + + exchanger.Transfer(3, false); + + var count1 = await task1; + + Assert.True(task1.IsCompleted); + Assert.False(task2.IsCompleted); + AssertASCII("Hel", new ArraySegment(buffer1, 0, count1)); + + exchanger.Transfer(0, true); + + var count2 = await task2; + + Assert.True(task1.IsCompleted); + Assert.True(task2.IsCompleted); + Assert.Equal(0, count2); + } + + + [Fact] + public async Task TransferFinFirstDoesReturnsCompletedReadAsyncs() + { + + var testInput = new TestInput(); + var context = new ConnectionContext(); + context.SocketInput = new SocketInput(new MemoryPool()); + + var exchanger = new MessageBodyExchanger(testInput.ConnectionContext); + + testInput.Add("Hello"); + + exchanger.Transfer(5, true); + + var buffer1 = new byte[1024]; + var buffer2 = new byte[1024]; + var task1 = exchanger.ReadAsync(new ArraySegment(buffer1)); + var task2 = exchanger.ReadAsync(new ArraySegment(buffer2)); + + Assert.True(task1.IsCompleted); + Assert.True(task2.IsCompleted); + + var count1 = await task1; + var count2 = await task2; + + AssertASCII("Hello", new ArraySegment(buffer1, 0, count1)); + Assert.Equal(0, count2); + } + + private void AssertASCII(string expected, ArraySegment actual) + { + var encoding = System.Text.Encoding.ASCII; + var bytes = encoding.GetBytes(expected); + Assert.Equal(bytes.Length, actual.Count); + for (var index = 0; index != bytes.Length; ++index) + { + Assert.Equal(bytes[index], actual.Array[actual.Offset + index]); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Server.KestralTests/MessageBodyTests.cs b/test/Microsoft.AspNet.Server.KestralTests/MessageBodyTests.cs new file mode 100644 index 0000000000..db1e3e3f89 --- /dev/null +++ b/test/Microsoft.AspNet.Server.KestralTests/MessageBodyTests.cs @@ -0,0 +1,64 @@ +using Microsoft.AspNet.Server.Kestrel.Http; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNet.Server.KestralTests +{ + /// + /// Summary description for MessageBodyTests + /// + public class MessageBodyTests + { + [Fact] + public async Task Http10ConnectionClose() + { + var input = new TestInput(); + var body = MessageBody.For("HTTP/1.0", new Dictionary(), input.ConnectionContext); + var stream = new FrameRequestStream(body); + + input.Add("Hello", true); + body.Consume(); + + var buffer1 = new byte[1024]; + var count1 = stream.Read(buffer1, 0, 1024); + AssertASCII("Hello", new ArraySegment(buffer1, 0, 5)); + + var buffer2 = new byte[1024]; + var count2 = stream.Read(buffer2, 0, 1024); + Assert.Equal(0, count2); + } + + [Fact] + public async Task Http10ConnectionCloseAsync() + { + var input = new TestInput(); + var body = MessageBody.For("HTTP/1.0", new Dictionary(), input.ConnectionContext); + var stream = new FrameRequestStream(body); + + input.Add("Hello", true); + body.Consume(); + + var buffer1 = new byte[1024]; + var count1 = await stream.ReadAsync(buffer1, 0, 1024); + AssertASCII("Hello", new ArraySegment(buffer1, 0, 5)); + + var buffer2 = new byte[1024]; + var count2 = await stream.ReadAsync(buffer2, 0, 1024); + Assert.Equal(0, count2); + } + + private void AssertASCII(string expected, ArraySegment actual) + { + var encoding = Encoding.ASCII; + var bytes = encoding.GetBytes(expected); + Assert.Equal(bytes.Length, actual.Count); + for (var index = 0; index != bytes.Length; ++index) + { + Assert.Equal(bytes[index], actual.Array[actual.Offset + index]); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Server.KestralTests/Microsoft.AspNet.Server.KestralTests.kproj b/test/Microsoft.AspNet.Server.KestralTests/Microsoft.AspNet.Server.KestralTests.kproj new file mode 100644 index 0000000000..1458edb016 --- /dev/null +++ b/test/Microsoft.AspNet.Server.KestralTests/Microsoft.AspNet.Server.KestralTests.kproj @@ -0,0 +1,37 @@ + + + + 12.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 37f3bfb2-6454-49e5-9d7f-581bf755ccfe + Console + + + ConsoleDebugger + + + WebDebugger + + + + + + + 2.0 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Server.KestralTests/NetworkingTests.cs b/test/Microsoft.AspNet.Server.KestralTests/NetworkingTests.cs index 2f17803fd4..4ccd07526c 100644 --- a/test/Microsoft.AspNet.Server.KestralTests/NetworkingTests.cs +++ b/test/Microsoft.AspNet.Server.KestralTests/NetworkingTests.cs @@ -5,6 +5,7 @@ using Microsoft.AspNet.Server.Kestrel.Networking; using System; using System.Net; using System.Net.Sockets; +using System.Runtime.InteropServices; using System.Threading.Tasks; using Xunit; @@ -113,14 +114,18 @@ namespace Microsoft.AspNet.Server.KestralTests var tcp2 = new UvTcpHandle(); tcp2.Init(loop); tcp.Accept(tcp2); - tcp2.ReadStart((__, nread, data, state2) => - { - bytesRead += nread; - if (nread == 0) + var data = Marshal.AllocCoTaskMem(500); + tcp2.ReadStart( + (a, b, c) => new Libuv.uv_buf_t { memory = data, len = 500 }, + (__, nread, state2) => { - tcp2.Close(); - } - }, null); + bytesRead += nread; + if (nread == 0) + { + tcp2.Close(); + } + }, + null); tcp.Close(); }, null); var t = Task.Run(async () => diff --git a/test/Microsoft.AspNet.Server.KestralTests/TestInput.cs b/test/Microsoft.AspNet.Server.KestralTests/TestInput.cs new file mode 100644 index 0000000000..4e920c8192 --- /dev/null +++ b/test/Microsoft.AspNet.Server.KestralTests/TestInput.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.AspNet.Server.Kestrel.Http; + +namespace Microsoft.AspNet.Server.KestralTests +{ + class TestInput + { + public TestInput() + { + var memory = new MemoryPool(); + ConnectionContext = new ConnectionContext + { + SocketInput = new SocketInput(memory), + Memory = memory, + }; + + } + public ConnectionContext ConnectionContext { get; set; } + + public void Add(string text, bool fin = false) + { + var encoding = System.Text.Encoding.ASCII; + var count = encoding.GetByteCount(text); + var buffer = ConnectionContext.SocketInput.Available(text.Length); + count = encoding.GetBytes(text, 0, text.Length, buffer.Array, buffer.Offset); + ConnectionContext.SocketInput.Extend(count); + if (fin) + { + ConnectionContext.SocketInput.RemoteIntakeFin = true; + } + } + } +} +