Not working, but worth taking a snapshot of the source

This commit is contained in:
Louis DeJardin 2014-06-06 22:13:31 -07:00
parent c9d6db14bc
commit e517e39aac
37 changed files with 3065 additions and 112 deletions

View File

@ -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

View File

@ -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>
/// Summary description for CallContext
/// </summary>
public class CallContext :
IHttpRequestFeature,
IHttpResponseFeature
{
public CallContext()
{
((IHttpResponseFeature)this).StatusCode = 200;
}
Stream IHttpResponseFeature.Body { get; set; }
Stream IHttpRequestFeature.Body { get; set; }
IDictionary<string, string[]> IHttpResponseFeature.Headers { get; set; }
IDictionary<string, string[]> 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<object> callback, object state)
{
}
}
}

View File

@ -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<UvStreamHandle, int, object> _readCallback = ReadCallback;
private static readonly Func<UvStreamHandle, int, object, Libuv.uv_buf_t> _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<object, Task> _app;
private readonly UvStreamHandle _socket;
private Frame _frame;
private Action<Exception> _fault;
private Action<Frame, Exception> _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;
}
}
}
}

View File

@ -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<string, string[]> _requestHeaders =
new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
readonly IDictionary<string, string[]> _responseHeaders =
new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
private MessageBody _messageBody;
private bool _resultStarted;
private bool _keepAlive;
private CallContext _callContext;
/*
//IDictionary<string, object> _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<Frame, Exception> HandleExpectContinue(Action<Frame, Exception> 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<byte>(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<string, object>();
//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<IDictionary<string, object>, Func<IDictionary<string, object>, 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<byte> data, Action<object> callback, object state)
{
ProduceStart();
_context.SocketOutput.Write(data, callback, state);
}
void Upgrade(IDictionary<string, object> options, Func<object, Task> 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<ArraySegment<byte>, IDisposable> CreateResponseHeader(
string status, IEnumerable<KeyValuePair<string, string[]>> 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<ArraySegment<byte>, 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<byte> 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();
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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<int>)asyncResult).Result;
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _body.ReadAsync(new ArraySegment<byte>(buffer, offset, count));
}
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state)
{
//NOTE todo
throw new NotImplementedException();
//var tcs = new TaskCompletionSource<int>(state);
//_body.ReadAsync(new ArraySegment<byte>(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; }
}
}

View File

@ -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<ArraySegment<byte>, Action<object>, object> _write;
public FrameResponseStream(Action<ArraySegment<byte>, Action<object>, object> write)
{
_write = write;
}
public override void Flush()
{
//_write(default(ArraySegment<byte>), null);
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<int>();
_write(new ArraySegment<byte>(new byte[0]), x => ((TaskCompletionSource<int>)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<int>();
_write(new ArraySegment<byte>(buffer, offset, count), x => ((TaskCompletionSource<int>)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; }
}
}

View File

@ -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<object, Task> Application { get; set; }
public IMemoryPool Memory { get; set; }
}
/// <summary>
/// Summary description for Accept
/// </summary>
public class Listener : IDisposable
public class Listener : ListenerContext, IDisposable
{
private readonly KestrelThread _thread;
UvTcpHandle _socket;
private readonly Action<UvStreamHandle, int, object> _connectionCallback = ConnectionCallback;
private static readonly Action<UvStreamHandle, int, object> _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<object, Task> app)
{
Thread = thread;
Application = app;
var tcs = new TaskCompletionSource<int>();
_thread.Post(OnStart, tcs);
Thread.Post(OnStart, tcs);
return tcs.Task;
}
@ -39,10 +61,10 @@ namespace Microsoft.AspNet.Server.Kestrel
var tcs = (TaskCompletionSource<int>)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();
}
}
}

View File

@ -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);
/// <summary>
/// Acquires a sub-segment of a larger memory allocation. Used for async sends of write-behind
/// buffers to reduce number of array segments pinned
/// </summary>
/// <param name = "minimumSize">The smallest length of the ArraySegment.Count that may be returned</param>
/// <returns>An array segment which is a sub-block of a larger allocation</returns>
ArraySegment<byte> AllocSegment(int minimumSize);
/// <summary>
/// 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.
/// </summary>
/// <param name = "segment">The sub-block that was originally returned by a call to AllocSegment.</param>
void FreeSegment(ArraySegment<byte> segment);
}
public class MemoryPool : IMemoryPool
{
static readonly byte[] EmptyArray = new byte[0];
class Pool<T>
{
readonly Stack<T[]> _stack = new Stack<T[]>();
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<byte> _pool1 = new Pool<byte>();
readonly Pool<byte> _pool2 = new Pool<byte>();
readonly Pool<char> _pool3 = new Pool<char>();
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<byte> AllocSegment(int minimumSize)
{
return new ArraySegment<byte>(AllocByte(minimumSize));
}
public void FreeSegment(ArraySegment<byte> segment)
{
FreeByte(segment.Array);
}
}
}

View File

@ -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<byte> Buffer
{
get
{
return new ArraySegment<byte>(_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);
}
}
}
}

View File

@ -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<T>(this Action<T> 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<string, string[]> 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<string, string[]> 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);
}
}
}
/// <summary>
/// http://tools.ietf.org/html/rfc2616#section-3.6.1
/// </summary>
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;
}
}
}
}

View File

@ -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>
/// Summary description for MessageBodyExchanger
/// </summary>
public class MessageBodyExchanger
{
private static readonly WaitCallback _completePending = CompletePending;
protected readonly ConnectionContext _context;
object _sync = new Object();
ArraySegment<byte> _buffer;
Queue<ReadOperation> _reads = new Queue<ReadOperation>();
public MessageBodyExchanger(ConnectionContext context)
{
_context = context;
_buffer = new ArraySegment<byte>(_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<byte>(
_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<int> ReadAsync(ArraySegment<byte> 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<byte>(_buffer.Array, _buffer.Offset + count, _buffer.Count - count);
return Task.FromResult(count);
}
else
{
// add ourselves to the line
var tcs = new TaskCompletionSource<int>();
_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<byte>(_buffer.Array, _buffer.Offset + count, _buffer.Count - count);
}
if (read.CompletionSource != null)
{
read.CompletionSource.SetResult(count);
}
return true;
}
public struct ReadOperation
{
public TaskCompletionSource<int> CompletionSource;
public ArraySegment<byte> Buffer;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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<byte>(_memory.Empty, 0, 0);
}
public ArraySegment<byte> Buffer { get; set; }
public bool RemoteIntakeFin { get; set; }
public void Skip(int count)
{
Buffer = new ArraySegment<byte>(Buffer.Array, Buffer.Offset + count, Buffer.Count - count);
}
public ArraySegment<byte> Take(int count)
{
var taken = new ArraySegment<byte>(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<byte>(_memory.Empty, 0, 0);
}
}
public ArraySegment<byte> Available(int minimumSize)
{
if (Buffer.Count == 0 && Buffer.Offset != 0)
{
Buffer = new ArraySegment<byte>(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<byte>(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<byte>(_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<byte>(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<byte>(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);
}
}
}

View File

@ -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
{
/// <summary>
/// Operations performed for buffered socket output
/// </summary>
public interface ISocketOutput
{
void Write(ArraySegment<byte> buffer, Action<object> 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<byte> buffer, Action<object> 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<UvWriteReq, int, object> _writeCallback = WriteCallback;
private static void WriteCallback(UvWriteReq req, int status, object state)
{
((ThisWriteReq)state).OnWrite(req, status);
}
SocketOutput _self;
ArraySegment<byte> _buffer;
Action<Exception> _drained;
UvStreamHandle _socket;
Action<object> _callback;
object _state;
GCHandle _pin;
internal void Contextualize(
SocketOutput socketOutput,
UvStreamHandle socket,
ArraySegment<byte> buffer,
Action<object> 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;
}
}
}

View File

@ -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
{

View File

@ -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;

View File

@ -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<KestrelThread>();
Listeners = new List<Listener>();
Memory = new MemoryPool();
Libuv = new Libuv();
Libuv.Load("libuv.dll");
}
public Libuv Libuv { get; private set; }
public IMemoryPool Memory { get; set; }
public List<KestrelThread> Threads { get; private set; }
public List<Listener> Listeners { get; private set; }
@ -44,13 +49,13 @@ namespace Microsoft.AspNet.Server.Kestrel
Threads.Clear();
}
public IDisposable CreateServer()
public IDisposable CreateServer(Func<object, Task> app)
{
var listeners = new List<Listener>();
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(() =>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>f510611a-3bee-4b88-a613-5f4a74ed82a1</ProjectGuid>
<OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Console'">
<DebuggerFlavor>ConsoleDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Web'">
<DebuggerFlavor>WebDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Content Include="Project.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="Http\CallContext.cs" />
<Compile Include="Http\FrameDuplexStream.cs" />
<Compile Include="Http\FrameRequestStream.cs" />
<Compile Include="Http\FrameResponseStream.cs" />
<Compile Include="Http\MemoryPoolTextWriter.cs" />
<Compile Include="Http\MessageBodyExchanger.cs" />
<Compile Include="Http\ReasonPhrases.cs" />
<Compile Include="Http\SocketInput.cs" />
<Compile Include="Http\Connection.cs" />
<Compile Include="Http\Frame.cs" />
<Compile Include="Http\Listener.cs" />
<Compile Include="Http\MemoryPool.cs" />
<Compile Include="Http\MessageBody.cs" />
<Compile Include="Http\SocketOutput.cs" />
<Compile Include="Infrastructure\Disposable.cs" />
<Compile Include="Infrastructure\KestrelThread.cs" />
<Compile Include="Networking\UcAsyncHandle.cs" />
<Compile Include="Networking\UvMemory.cs" />
<Compile Include="Networking\UvShutdownReq.cs" />
<Compile Include="Networking\UvTcpHandle.cs" />
<Compile Include="Networking\UvStreamHandle.cs" />
<Compile Include="Networking\UvHandle.cs" />
<Compile Include="Networking\Libuv.cs" />
<Compile Include="Networking\UvLoopHandle.cs" />
<Compile Include="KestrelEngine.cs" />
<Compile Include="Networking\UvWriteRequest.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -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;
}
}

View File

@ -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<THandle>(IntPtr handle)
{
GCHandle gcHandle = GCHandle.FromIntPtr(*(IntPtr*)handle);
return (THandle)gcHandle.Target;
}
public void Reference()
{
_uv.@ref(this);

View File

@ -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);

View File

@ -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>
/// Summary description for UvMemory
/// </summary>
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<THandle>(IntPtr handle)
{
GCHandle gcHandle = GCHandle.FromIntPtr(*(IntPtr*)handle);
return (THandle)gcHandle.Target;
}
}
}

View File

@ -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>
/// Summary description for UvShutdownRequest
/// </summary>
public class UvShutdownReq : UvReq
{
private readonly static Libuv.uv_shutdown_cb _uv_shutdown_cb = UvShutdownCb;
Action<UvShutdownReq, int, object> _callback;
object _state;
public void Init(UvLoopHandle loop)
{
CreateHandle(loop.Libuv, loop.Libuv.req_size(3));
}
public void Shutdown(UvStreamHandle handle, Action<UvShutdownReq, int, object> 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<UvShutdownReq>(ptr);
req._callback(req, status, req._state);
req._callback = null;
req._state = null;
}
}
}

View File

@ -15,12 +15,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
public Action<UvStreamHandle, int, object> _connectionCallback;
public object _connectionState;
public Action<UvStreamHandle, int, byte[], object> _readCallback;
public Func<UvStreamHandle, int, object, Libuv.uv_buf_t> _allocCallback;
public Action<UvStreamHandle, int, object> _readCallback;
public object _readState;
public UvStreamHandle()
{
}
public void Listen(int backlog, Action<UvStreamHandle, int, object> callback, object state)
{
@ -35,10 +34,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
}
public void ReadStart(
Action<UvStreamHandle, int, byte[], object> callback,
Func<UvStreamHandle, int, object, Libuv.uv_buf_t> allocCallback,
Action<UvStreamHandle, int, object> 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<UvStreamHandle>(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<UvStreamHandle>(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<UvStreamHandle>(ptr);
var stream = FromIntPtr<UvStreamHandle>(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);
}
}
}

View File

@ -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>
/// Summary description for UvWriteRequest
/// </summary>
public class UvWriteReq : UvReq
{
private readonly static Libuv.uv_write_cb _uv_write_cb = UvWriteCb;
Action<UvWriteReq, int, object> _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<UvWriteReq, int, object> 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<UvWriteReq>(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;
}
}
}

View File

@ -1,6 +1,7 @@
{
"version": "0.1-alpha-*",
"dependencies": {
"Microsoft.AspNet.Hosting": "0.1-*"
},
"compilationOptions": {
"allowUnsafe": true

36
src/SampleApp/Program.cs Normal file
View File

@ -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");
}
}
}

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>2c3cb3dc-eebf-4f52-9e1c-4f2f972e76c3</ProjectGuid>
<OutputType>Console</OutputType>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Console'">
<DebuggerFlavor>ConsoleDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Web'">
<DebuggerFlavor>WebDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" Label="Configuration">
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="libuv.dll" />
<Content Include="project.json" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

BIN
src/SampleApp/libuv.dll Normal file

Binary file not shown.

View File

@ -0,0 +1,14 @@
{
"dependencies": {
"Microsoft.AspNet.Server.Kestrel": "0.1-alpha-*",
"Microsoft.AspNet.PipelineCore": "0.1-alpha-*"
},
"configurations" : {
"net45" : { },
"k10" : {
"dependencies": {
"System.Console": "4.0.0.0"
}
}
}
}

View File

@ -1,5 +1,9 @@
using Microsoft.AspNet.Server.Kestrel;
using Microsoft.AspNet.HttpFeature;
using Microsoft.AspNet.Server.Kestrel;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Xunit;
@ -10,21 +14,61 @@ namespace Microsoft.AspNet.Server.KestralTests
/// </summary>
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();
}
}
}
}

View File

@ -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>
/// Summary description for MessageBodyExchangerTests
/// </summary>
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<byte>(buffer1));
var task2 = exchanger.ReadAsync(new ArraySegment<byte>(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<byte>(buffer1, 0, count1));
exchanger.Transfer(2, false);
var count2 = await task2;
Assert.True(task1.IsCompleted);
Assert.True(task2.IsCompleted);
AssertASCII("lo", new ArraySegment<byte>(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<byte>(buffer1));
var task2 = exchanger.ReadAsync(new ArraySegment<byte>(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<byte>(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<byte>(buffer1));
var task2 = exchanger.ReadAsync(new ArraySegment<byte>(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<byte>(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<byte>(buffer1));
var task2 = exchanger.ReadAsync(new ArraySegment<byte>(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<byte>(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<byte>(buffer1));
var task2 = exchanger.ReadAsync(new ArraySegment<byte>(buffer2));
Assert.True(task1.IsCompleted);
Assert.True(task2.IsCompleted);
var count1 = await task1;
var count2 = await task2;
AssertASCII("Hello", new ArraySegment<byte>(buffer1, 0, count1));
Assert.Equal(0, count2);
}
private void AssertASCII(string expected, ArraySegment<byte> 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]);
}
}
}
}

View File

@ -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>
/// Summary description for MessageBodyTests
/// </summary>
public class MessageBodyTests
{
[Fact]
public async Task Http10ConnectionClose()
{
var input = new TestInput();
var body = MessageBody.For("HTTP/1.0", new Dictionary<string, string[]>(), 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<byte>(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<string, string[]>(), 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<byte>(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<byte> 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]);
}
}
}
}

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>37f3bfb2-6454-49e5-9d7f-581bf755ccfe</ProjectGuid>
<OutputType>Console</OutputType>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Console'">
<DebuggerFlavor>ConsoleDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Web'">
<DebuggerFlavor>WebDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Content Include="Project.json" />
<Content Include="libuv.dll" />
</ItemGroup>
<ItemGroup>
<Compile Include="EngineTests.cs" />
<Compile Include="MessageBodyExchangerTests.cs" />
<Compile Include="MessageBodyTests.cs" />
<Compile Include="NetworkingTests.cs" />
<Compile Include="TestInput.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -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 () =>

View File

@ -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;
}
}
}
}