ln.http/ln.http/HttpRouter.cs

199 lines
6.9 KiB
C#

using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
namespace ln.http
{
public delegate bool HttpRouterDelegate(HttpContext context);
public delegate bool HttpAuthorizationDelegate(HttpContext httpContext);
public delegate void HttpFilterDelegate(HttpContext httpContext);
public enum HttpRoutePriority : int { HIGHEST = 0, HIGH = 1, NORMAL = 2, LOW = 3, LOWEST = 4 }
public class HttpRouter : IDisposable
{
public event HttpFilterDelegate HttpFilters;
private List<HttpMapping>[] _mappings = new List<HttpMapping>[5];
private HttpServer _httpServer;
private HttpRouter _parentRouter;
private HttpMapping _parentMapping;
public HttpRouter()
{
for (int n = 0; n < 5; n++)
_mappings[n] = new List<HttpMapping>();
}
public HttpRouter(HttpServer httpServer)
: this()
{
_httpServer = httpServer;
httpServer.AddRouter(this);
}
public HttpRouter(HttpRouter parentRouter, string mappingPath)
{
_parentRouter = parentRouter;
_parentMapping = _parentRouter.Map(HttpMethod.ANY, mappingPath, this.Route);
}
public HttpMapping Map(HttpMethod httpMethod, string uri, HttpRouterDelegate routerDelegate) =>
Map(httpMethod, uri, HttpRoutePriority.NORMAL, routerDelegate);
public HttpMapping Map(HttpMethod httpMethod, string uri, HttpRoutePriority priority, HttpRouterDelegate routerDelegate)
{
HttpMapping httpMapping = new HttpMapping(httpMethod, RegexFromRoute(uri), routerDelegate);
_mappings[(int)priority].Add(httpMapping);
return httpMapping;
}
public bool RemoveHttpMapping(HttpMapping httpMapping)
{
foreach (var mappings in _mappings)
{
if (mappings.Contains(httpMapping))
return mappings.Remove(httpMapping);
}
return false;
}
public bool Route(HttpContext httpContext)
{
string residual = "";
HttpFilters?.Invoke(httpContext);
for (int n = 0; n < _mappings.Length; n++)
{
foreach (HttpMapping httpMapping in _mappings[n])
{
if ((httpMapping.Method == httpContext.Request?.Method) || (httpMapping.Method == HttpMethod.ANY))
{
Match match = httpMapping.UriRegex.Match(httpContext.RoutableUri);
if (match.Success)
{
foreach (Group group in match.Groups)
{
httpContext.Request?.SetParameter(group.Name, group.Value);
if (group.Name.Equals("_"))
if (group.Value.StartsWith("/", StringComparison.InvariantCulture))
residual = group.Value;
else
residual = "/" + group.Value;
}
string saveRoutableUri = httpContext.RoutableUri;
httpContext.RoutableUri = residual;
if (httpMapping.Route(httpContext))
return true;
httpContext.RoutableUri = saveRoutableUri;
}
}
}
if (httpContext.Response is not null)
break;
}
return false;
}
private string RegexFromRoute(string route)
{
string[] parts = route.Split(new char[] { '/' });
string[] reparts = parts.Select((part) =>
{
if (part.StartsWith(":", StringComparison.InvariantCulture))
if (part.EndsWith("*", StringComparison.InvariantCulture))
return string.Format("(?<{0}>[^/]+)(?<_>/.*)?", part.Substring(1, part.Length - 2));
else
return string.Format("(?<{0}>[^/]+)", part.Substring(1));
else if (part.Equals("*"))
return string.Format("(?<_>.*)");
else
return string.Format("{0}", part);
}).ToArray();
return string.Format("^{0}/?$", string.Join("/", reparts));
}
public class HttpMapping
{
public HttpMethod Method { get; set; }
public Regex UriRegex { get; set; }
public HttpRouterDelegate RouterDelegate { get; set; }
public bool AuthenticationRequired { get; set; }
public HttpAuthorizationDelegate AuthorizationDelegate { get; set; }
public event HttpFilterDelegate HttpFilters;
public HttpMapping(HttpMethod httpMethod, string reUri, HttpRouterDelegate routerDelegate)
{
Method = httpMethod;
UriRegex = new Regex(reUri);
RouterDelegate = routerDelegate;
AuthenticationRequired = HttpServer.DefaultRouteAuthentication;
}
public HttpMapping UseFilter(HttpFilterDelegate filterDelegate)
{
HttpFilters += filterDelegate;
return this;
}
public HttpMapping Authenticate()
{
AuthenticationRequired = true;
return this;
}
public HttpMapping Anonymous()
{
AuthenticationRequired = false;
return this;
}
public HttpMapping Authorize(HttpAuthorizationDelegate authorizationDelegate)
{
AuthorizationDelegate = authorizationDelegate;
return this;
}
public bool Route(HttpContext httpContext)
{
if (AuthenticationRequired && httpContext.AuthenticatedPrincipal is null)
{
httpContext.Response = HttpResponse.Unauthorized().Header("WWW-Authenticate", "Basic");
return true;
}
if ((AuthorizationDelegate is not null) && (!AuthorizationDelegate(httpContext)))
return false;
HttpFilters?.Invoke(httpContext);
return RouterDelegate(httpContext);
}
}
public void Dispose()
{
_httpServer?.RemoveRouter(this);
_httpServer = null;
_parentRouter?.RemoveHttpMapping(_parentMapping);
_parentMapping = null;
_parentRouter = null;
}
}
}