Last active
February 3, 2019 05:06
-
-
Save davidfowl/ee7c4b2a668cdc17dcc378bb389aac64 to your computer and use it in GitHub Desktop.
Transport abstraction + Kestrel's http parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Net; | |
using System.Text; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Microsoft.AspNetCore.Hosting; | |
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; | |
using Microsoft.AspNetCore.Server.Kestrel.Internal.System; | |
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines; | |
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; | |
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv; | |
using Microsoft.Extensions.Logging; | |
using Microsoft.Extensions.Options; | |
namespace WebApplication28 | |
{ | |
public class Program | |
{ | |
public static void Main(string[] args) | |
{ | |
// Single threaded because the cool kids do that | |
var options = new LibuvTransportOptions | |
{ | |
ThreadCount = 1 | |
}; | |
var port = new Uri(Environment.GetEnvironmentVariable("ASPNETCORE_URLS")).Port; | |
var loggerFactory = new LoggerFactory(); | |
loggerFactory.AddConsole(LogLevel.Debug); | |
// This is kinda hacky | |
var lifetime = new Lifetime(); | |
var transportFactory = new LibuvTransportFactory(Options.Create(options), lifetime, loggerFactory); | |
var endPoint = new IPEndPointInformation(new IPEndPoint(IPAddress.Loopback, port)); | |
ExecuteAsync(transportFactory, endPoint, new HttpHandler()).Wait(); | |
} | |
private static async Task ExecuteAsync(ITransportFactory transportFactory, IEndPointInformation info, IConnectionHandler handler) | |
{ | |
var transport = transportFactory.Create(info, handler); | |
try | |
{ | |
await transport.BindAsync(); | |
Console.WriteLine($"Waiting for connections on {info}"); | |
Console.ReadLine(); | |
} | |
finally | |
{ | |
await transport.UnbindAsync(); | |
await transport.StopAsync(); | |
} | |
} | |
} | |
public class HttpHandler : IConnectionHandler | |
{ | |
public IConnectionContext OnConnection(IConnectionInformation connectionInfo) | |
{ | |
var inputOptions = new PipeOptions { WriterScheduler = connectionInfo.InputWriterScheduler }; | |
var outputOptions = new PipeOptions { ReaderScheduler = connectionInfo.OutputReaderScheduler }; | |
var context = new HttpConnectionContext | |
{ | |
ConnectionId = Guid.NewGuid().ToString(), | |
Input = connectionInfo.PipeFactory.Create(inputOptions), | |
Output = connectionInfo.PipeFactory.Create(outputOptions) | |
}; | |
_ = context.ExecuteAsync(); | |
return context; | |
} | |
private class HttpConnectionContext : IConnectionContext, IHttpHeadersHandler, IHttpRequestLineHandler | |
{ | |
private State _state; | |
public string ConnectionId { get; set; } | |
public IPipe Input { get; set; } | |
public IPipe Output { get; set; } | |
IPipeWriter IConnectionContext.Input => Input.Writer; | |
IPipeReader IConnectionContext.Output => Output.Reader; | |
public void Abort(Exception ex) | |
{ | |
} | |
public void OnConnectionClosed() | |
{ | |
// The transport closed the connection | |
} | |
public async Task ExecuteAsync() | |
{ | |
try | |
{ | |
var parser = new HttpParser<HttpConnectionContext>(); | |
while (true) | |
{ | |
var result = await Input.Reader.ReadAsync(); | |
var inputBuffer = result.Buffer; | |
var consumed = inputBuffer.Start; | |
var examined = inputBuffer.End; | |
try | |
{ | |
if (inputBuffer.IsEmpty && result.IsCompleted) | |
{ | |
break; | |
} | |
ParseHttpRequest(parser, inputBuffer, out consumed, out examined); | |
if (_state != State.Body && result.IsCompleted) | |
{ | |
// Bad request | |
break; | |
} | |
if (_state == State.Body) | |
{ | |
var outputBuffer = Output.Writer.Alloc(); | |
outputBuffer.Write(Encoding.UTF8.GetBytes("HTTP/1.1 200 OK")); | |
outputBuffer.Write(Encoding.UTF8.GetBytes("\r\nContent-Length: 13")); | |
outputBuffer.Write(Encoding.UTF8.GetBytes("\r\nContent-Type: text/plain")); | |
outputBuffer.Write(Encoding.UTF8.GetBytes("\r\n\r\n")); | |
outputBuffer.Write(Encoding.UTF8.GetBytes("Hello, World!")); | |
await outputBuffer.FlushAsync(); | |
_state = State.StartLine; | |
} | |
} | |
finally | |
{ | |
Input.Reader.Advance(consumed, examined); | |
} | |
} | |
Input.Reader.Complete(); | |
} | |
catch (Exception ex) | |
{ | |
Input.Reader.Complete(ex); | |
} | |
finally | |
{ | |
Output.Writer.Complete(); | |
} | |
} | |
private void ParseHttpRequest(HttpParser<HttpConnectionContext> parser, ReadableBuffer inputBuffer, out ReadCursor consumed, out ReadCursor examined) | |
{ | |
consumed = inputBuffer.Start; | |
examined = inputBuffer.End; | |
if (_state == State.StartLine) | |
{ | |
if (parser.ParseRequestLine(this, inputBuffer, out consumed, out examined)) | |
{ | |
_state = State.Headers; | |
inputBuffer = inputBuffer.Slice(consumed); | |
} | |
} | |
if (_state == State.Headers) | |
{ | |
if (parser.ParseHeaders(this, inputBuffer, out consumed, out examined, out int consumedBytes)) | |
{ | |
_state = State.Body; | |
} | |
} | |
} | |
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded) | |
{ | |
} | |
public void OnHeader(Span<byte> name, Span<byte> value) | |
{ | |
} | |
private enum State | |
{ | |
StartLine, | |
Headers, | |
Body | |
} | |
} | |
} | |
public class IPEndPointInformation : IEndPointInformation | |
{ | |
public IPEndPointInformation(IPEndPoint endPoint) | |
{ | |
IPEndPoint = endPoint; | |
} | |
public ListenType Type => ListenType.IPEndPoint; | |
public IPEndPoint IPEndPoint { get; set; } | |
public string SocketPath => null; | |
public ulong FileHandle => 0; | |
public bool NoDelay { get; set; } = true; | |
public override string ToString() | |
{ | |
return IPEndPoint?.ToString(); | |
} | |
} | |
public class Lifetime : IApplicationLifetime | |
{ | |
public CancellationToken ApplicationStarted => throw new NotImplementedException(); | |
public CancellationToken ApplicationStopping => throw new NotImplementedException(); | |
public CancellationToken ApplicationStopped => throw new NotImplementedException(); | |
public void StopApplication() | |
{ | |
throw new NotImplementedException(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment