243 lines
8.6 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|