Rest.Net/Rest.Net/RouteListener.cs

243 lines
8.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Threading;
namespace MontoyaTech.Rest.Net
{
/// <summary>
/// The outline of a Route listener that listens for http requests and invokes
/// matching routes.
/// </summary>
public class RouteListener
{
/// <summary>
/// The internal http listener.
/// </summary>
private HttpListener HttpListener = null;
/// <summary>
/// The list of routes this RouteListener is listening for.
/// </summary>
public List<Route> Routes = new List<Route>();
/// <summary>
/// The port this RouteListener is listening on.
/// </summary>
public ushort Port = 8081;
/// <summary>
/// Returns the BaseUrl for this RouteListener.
/// </summary>
public string BaseUrl
{
get
{
return $"http://localhost:{this.Port}";
}
}
/// <summary>
/// An event to preprocess routes before the route is executed.
/// </summary>
public event RequestPreProcessEventHandler RequestPreProcessEvent;
/// <summary>
/// An event to postprocess routes before the route response is returned.
/// </summary>
public event RequestPostProcessEventHandler RequestPostProcessEvent;
/// <summary>
/// An event that is invoked when a route has an unhandled exception.
/// </summary>
public event RouteExceptionEventHandler RouteExceptionEvent;
/// <summary>
/// Creates a new RouteListener with the default port number.
/// </summary>
public RouteListener()
{
}
/// <summary>
/// Creates a nwe RouteListener with a series of routes.
/// </summary>
/// <param name="routes">The routes to listen for.</param>
public RouteListener(params Route[] routes)
{
this.Routes = routes.ToList();
}
/// <summary>
/// Creates a new RouteListener with a port to listen on and a series of routes.
/// </summary>
/// <param name="port">The port number the listener should use.</param>
/// <param name="routes">The routes to listen for.</param>
public RouteListener(ushort port, params Route[] routes)
{
this.Routes = routes.ToList();
this.Port = port;
}
/// <summary>
/// Starts this RouteListener if it's not already running.
/// </summary>
public void Start()
{
if (this.HttpListener == null)
{
this.HttpListener = new HttpListener();
//Support listening on Windows & Linux.
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
this.HttpListener.Prefixes.Add($"http://localhost:{this.Port}/");
this.HttpListener.Prefixes.Add($"http://127.0.0.1:{this.Port}/");
}
else
{
this.HttpListener.Prefixes.Add($"http://*:{this.Port}/");
}
this.HttpListener.Start();
this.Listen();
}
}
/// <summary>
/// Function that does the actual listening.
/// </summary>
private void Listen()
{
ThreadPool.QueueUserWorkItem((o) =>
{
while (this.HttpListener.IsListening)
{
//Try catch around the thread pool, don't allow it to die.
try
{
ThreadPool.QueueUserWorkItem((item) =>
{
var context = item as HttpListenerContext;
try
{
bool handled = false;
bool close = true;
string[] arguments = null;
//Preprocess the route context, if it returns false, then we have to not invoke the route.
try
{
if (this.RequestPreProcessEvent != null && !this.RequestPreProcessEvent.Invoke(context))
handled = true;
}
catch { }
for (int i = 0; i < this.Routes.Count && !handled; i++)
{
if (this.Routes[i].Method.ToUpper() == context.Request.HttpMethod.ToUpper() && RouteMatcher.Matches(context.Request.Url.AbsolutePath, this.Routes[i].Syntax, out arguments))
{
handled = true;
close = this.Routes[i].CloseResponse;
//Make sure if the route fails we don't die here, just set the response to internal server error.
try
{
this.Routes[i].Invoke(context, arguments);
}
catch (Exception ex)
{
if (this.RouteExceptionEvent != null)
this.RouteExceptionEvent.Invoke(this.Routes[i], context, ex);
context.Response.WithStatus(HttpStatusCode.InternalServerError);
}
break;
}
}
//Post process the route context.
try
{
if (this.RequestPostProcessEvent != null)
this.RequestPostProcessEvent.Invoke(context);
}
catch { }
if (!handled)
context.Response.WithStatus(HttpStatusCode.NotFound);
if (close)
context.Response.Close();
}
catch (Exception ex)
{
context.Response.WithStatus(HttpStatusCode.InternalServerError);
context.Response.Close();
}
}, this.HttpListener.GetContext());
}
catch { }
}
});
}
/// <summary>
/// Stops this RouteListener if it's running.
/// </summary>
public void Stop()
{
if (this.HttpListener != null)
{
try
{
this.HttpListener.Stop();
this.HttpListener = null;
}
catch
{
this.HttpListener = null;
}
}
}
/// <summary>
/// Blocks the current thread indenfinitly while this RouteListner is running.
/// </summary>
public void Block()
{
try
{
while (this.HttpListener != null && this.HttpListener.IsListening && Thread.CurrentThread.ThreadState != ThreadState.AbortRequested && Thread.CurrentThread.ThreadState != ThreadState.Aborted)
Thread.Sleep(100);
}
catch { }
}
/// <summary>
/// Generates a C# client from this Route Listener and returns the code.
/// </summary>
/// <param name="clientName">The name of the Client. Default is Client.</param>
/// <param name="staticCode">Whether or not to generate a static client.</param>
/// <returns></returns>
public string GenerateCSharpClient(string clientName = "Client", bool staticCode =false)
{
var generator = new RestCSharpClientGenerator();
generator.ClientName = clientName;
generator.StaticCode = staticCode;
return generator.Generate(this);
}
}
}