Skip to content

Instantly share code, notes, and snippets.

@davidfowl
Last active February 3, 2019 05:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidfowl/ee7c4b2a668cdc17dcc378bb389aac64 to your computer and use it in GitHub Desktop.
Save davidfowl/ee7c4b2a668cdc17dcc378bb389aac64 to your computer and use it in GitHub Desktop.
Transport abstraction + Kestrel's http parser
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