Skip to content

Instantly share code, notes, and snippets.

@Porges
Created July 23, 2013 20:42
Show Gist options
  • Save Porges/6066000 to your computer and use it in GitHub Desktop.
Save Porges/6066000 to your computer and use it in GitHub Desktop.
sketch of C# version of example program
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace ExampleApplication
{
public class Haywire
{
private readonly List<RouteCallback> _routeCallbacks = new List<RouteCallback>(); // store these in here to prevent them being GC'd
private ResponseCompleteCallback _responseCompleteCallback; // ditto, but I've assumed only one can be active at any one time
[StructLayout(LayoutKind.Sequential)]
unsafe struct NativeHaywireConfig
{
IntPtr http_listen_address;
int http_listen_port;
public NativeHaywireConfig(HaywireConfig config)
{
http_listen_address = Marshal.StringToHGlobalAnsi(config.ListenAddress.ToString());
http_listen_port = config.ListenPort;
}
}
[DllImport("haywire.dll", CallingConvention = CallingConvention.Cdecl)]
private unsafe static extern void hw_init_with_config(NativeHaywireConfig* config);
public Haywire(HaywireConfig haywireConfig)
{
// a dummy handler always needs to be registered to stop UserData from leaking
RegisterHttpResponseCompleteCallback(_ => { });
var nativeHaywireConfig = new NativeHaywireConfig(haywireConfig);
unsafe
{
hw_init_with_config(&nativeHaywireConfig);
}
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void RouteCallback(IntPtr request, IntPtr response);
[DllImport("haywire.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void hw_http_add_route(string route, RouteCallback callback);
public void AddRoute(string route, Action<HttpRequest, HttpResponse> callback)
{
var nativeCallback = new RouteCallback((req, resp) =>
{
var httpReq = new HttpRequest(req);
var httpResp = new HttpResponse(resp);
try
{
callback(httpReq, httpResp);
}
finally
{
httpReq.Invalidate();
httpResp.Invalidate();
}
});
hw_http_add_route(route, nativeCallback);
_routeCallbacks.Add(nativeCallback); // TODO: need to evict things from here when no longer needed?
}
[DllImport("haywire.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void hw_register_http_response_complete_callback(ResponseCompleteCallback callback);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void ResponseCompleteCallback(IntPtr userData);
public void RegisterHttpResponseCompleteCallback(Action<object> callback)
{
var nativeCallback = new ResponseCompleteCallback(ptr =>
{
var handle = GCHandle.FromIntPtr(ptr);
var obj = handle.Target;
handle.Free();
callback(obj);
});
hw_register_http_response_complete_callback(nativeCallback);
_responseCompleteCallback = nativeCallback;
}
[DllImport("haywire.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void hw_http_open();
public void Open()
{
hw_http_open();
}
}
}
using System.Net;
namespace ExampleApplication
{
public class HaywireConfig
{
public IPAddress ListenAddress { get; set; }
public int ListenPort { get; set; }
}
}
using System;
using System.Runtime.InteropServices;
namespace ExampleApplication
{
public unsafe class HttpRequest
{
[StructLayout(LayoutKind.Sequential)]
struct HttpRequestNative
{
// flesh out if needed - TODO make sure sizeofs are the same here as in C
ushort http_major;
ushort http_minor;
byte method;
public int keep_alive;
}
private HttpRequestNative* _me;
internal HttpRequest(IntPtr pointer)
{
_me = (HttpRequestNative*)pointer;
}
internal void Invalidate()
{
_me = null;
}
private void CheckIfInvalid()
{
if (_me == null) throw new InvalidOperationException("Cannot use HttpRequest outside a route handler.");
}
public bool KeepAlive
{
get
{
CheckIfInvalid();
return _me->keep_alive != 0;
}
}
}
}
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace ExampleApplication
{
public unsafe class HttpResponse
{
struct HttpResponseNative
{
// flesh out if needed
}
private HttpResponseNative* _me;
private int _sent;
internal HttpResponse(IntPtr pointer)
{
_me = (HttpResponseNative*) pointer;
}
internal void Invalidate()
{
_me = null;
}
private void CheckIfInvalid()
{
if (_me == null) throw new InvalidOperationException("Cannot use HttpResponse outside a route handler.");
}
[DllImport("haywire.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void hw_set_response_status_code(HttpResponseNative* response, HwString* statusCode);
public string StatusCode
{
set
{
CheckIfInvalid();
hw_set_response_status_code(_me, HwString.AllocateFromString(value));
}
}
[DllImport("haywire.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern void hw_set_response_header(HttpResponseNative* response, HwString* key, HwString* value);
public void AddHeader(string key, string value)
{
CheckIfInvalid();
hw_set_response_header(_me, HwString.AllocateFromString(key), HwString.AllocateFromString(value));
}
[DllImport("haywire.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void hw_set_body(HttpResponseNative* response, HwString* body);
public string Body
{
set
{
CheckIfInvalid();
hw_set_body(_me, HwString.AllocateFromString(value));
}
}
[DllImport("haywire.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void hw_set_http_version(HttpResponseNative* response, int major, int minor);
public Version HttpVersion
{
set
{
CheckIfInvalid();
hw_set_http_version(_me, value.Major, value.Minor);
}
}
[DllImport("haywire.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void hw_http_response_send(HttpResponseNative* response, IntPtr userData);
public void Send(object userData)
{
CheckIfInvalid();
if (Interlocked.Exchange(ref _sent, 1) == 1)
throw new InvalidOperationException("Already sent a response.");
var handle = GCHandle.Alloc(userData);
hw_http_response_send(_me, (IntPtr)handle);
}
}
}
using System;
using System.Runtime.InteropServices;
namespace ExampleApplication
{
[StructLayout(LayoutKind.Sequential)]
unsafe internal struct HwString
{
public IntPtr value;
public UIntPtr length;
public static HwString* AllocateFromString(string value)
{
var hwstring = (HwString*)Marshal.AllocHGlobal(sizeof(HwString));
hwstring->value = Marshal.StringToHGlobalAnsi(value);
hwstring->length = (UIntPtr)value.Length;
return hwstring;
}
}
}
using System;
using System.Net;
namespace ExampleApplication
{
class Program
{
private static void GetRoot(HttpRequest request, HttpResponse response)
{
var responseString = "hello world";
response.StatusCode = "200 OK";
response.AddHeader("Content-Type", "text/html");
response.Body = responseString;
if (request.KeepAlive)
{
response.AddHeader("Connection", "Keep-Alive");
}
else
{
response.HttpVersion = new Version(1, 0); // hah
}
response.Send(responseString);
}
static void ResponseComplete(object userData)
{
}
private static void Main(string[] args)
{
var route = "/";
var config = new HaywireConfig
{
ListenAddress = IPAddress.Any,
ListenPort = 8000,
};
if (args.Length > 0)
{
config.ListenPort = int.Parse(args[0]);
}
var haywire = new Haywire(config);
haywire.AddRoute(route, GetRoot);
haywire.RegisterHttpResponseCompleteCallback(ResponseComplete);
haywire.Open();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment