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 HttpAuthenticationDelegate(HttpContext httpContext, out HttpPrincipal principal); 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; public event HttpAuthenticationDelegate AuthenticationDelegates; private List[] _mappings = new List[5]; private HttpServer _httpServer; public HttpRouter() { for (int n = 0; n < 5; n++) _mappings[n] = new List(); } public HttpRouter(HttpServer httpServer) : this() { _httpServer = httpServer; httpServer.AddRouter(this); } 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 Route(HttpContext httpContext) { string residual = ""; if (AuthenticationDelegates is not null) { foreach (HttpAuthenticationDelegate authenticationDelegate in AuthenticationDelegates.GetInvocationList()) { if (httpContext.Authenticate(authenticationDelegate)) break; } } 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) return false; if ((AuthorizationDelegate is not null) && (!AuthorizationDelegate(httpContext))) return false; HttpFilters?.Invoke(httpContext); return RouterDelegate(httpContext); } } public void Dispose() { _httpServer?.RemoveRouter(this); } } }