EonaCat.Network/EonaCat.Network/System/Web/DynamicRouteManager.cs

163 lines
5.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using RegexMatcher;
namespace EonaCat.Network
{
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
/// <summary>
/// Dynamic route manager. Dynamic routes are used for requests using any HTTP method to any path that can be matched by regular expression.
/// </summary>
public class DynamicRouteManager
{
/// <summary>
/// Directly access the underlying regular expression matching library.
/// This is helpful in case you want to specify the matching behavior should multiple matches exist.
/// </summary>
public Matcher Matcher
{
get
{
return _Matcher;
}
}
private Matcher _Matcher = new Matcher();
private readonly object _Lock = new object();
private Dictionary<DynamicRoute, Func<HttpContext, Task>> _Routes = new Dictionary<DynamicRoute, Func<HttpContext, Task>>();
/// <summary>
/// Instantiate the object.
/// </summary>
public DynamicRouteManager()
{
_Matcher.MatchPreference = MatchPreferenceType.LongestFirst;
}
/// <summary>
/// Add a route.
/// </summary>
/// <param name="method">The HTTP method.</param>
/// <param name="path">URL path, i.e. /path/to/resource.</param>
/// <param name="handler">Method to invoke.</param>
/// <param name="guid">Globally-unique identifier.</param>
/// <param name="metadata">User-supplied metadata.</param>
public void Add(HttpMethod method, Regex path, Func<HttpContext, Task> handler, string guid = null, object metadata = null)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (_Lock)
{
DynamicRoute dr = new DynamicRoute(method, path, handler);
_Matcher.Add(
new Regex(BuildConsolidatedRegex(method, path)),
dr);
_Routes.Add(new DynamicRoute(method, path, handler, guid, metadata), handler);
}
}
/// <summary>
/// Remove a route.
/// </summary>
/// <param name="method">The HTTP method.</param>
/// <param name="path">URL path.</param>
public void Remove(HttpMethod method, Regex path)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
lock (_Lock)
{
_Matcher.Remove(
new Regex(BuildConsolidatedRegex(method, path)));
if (_Routes.Any(r => r.Key.Method == method && r.Key.Path.Equals(path)))
{
List<DynamicRoute> removeList = _Routes.Where(r => r.Key.Method == method && r.Key.Path.Equals(path))
.Select(r => r.Key)
.ToList();
foreach (DynamicRoute remove in removeList)
{
_Routes.Remove(remove);
}
}
}
}
/// <summary>
/// Check if a content route exists.
/// </summary>
/// <param name="method">The HTTP method.</param>
/// <param name="path">URL path.</param>
/// <returns>True if exists.</returns>
public bool Exists(HttpMethod method, Regex path)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
lock (_Lock)
{
return _Routes.Any(r => r.Key.Method == method && r.Key.Path.Equals(path));
}
}
/// <summary>
/// Match a request method and URL to a handler method.
/// </summary>
/// <param name="method">The HTTP method.</param>
/// <param name="rawUrl">URL path.</param>
/// <param name="dr">Matching route.</param>
/// <returns>Method to invoke.</returns>
public Func<HttpContext, Task> Match(HttpMethod method, string rawUrl, out DynamicRoute dr)
{
dr = null;
if (String.IsNullOrEmpty(rawUrl))
throw new ArgumentNullException(nameof(rawUrl));
object val = null;
if (_Matcher.Match(
BuildConsolidatedRegex(method, rawUrl),
out val))
{
if (val == null)
{
return null;
}
else
{
lock (_Lock)
{
dr = (DynamicRoute)val;
return dr.Handler;
}
}
}
return null;
}
private string BuildConsolidatedRegex(HttpMethod method, string rawUrl)
{
rawUrl = rawUrl.Replace("^", "");
return method.ToString() + " " + rawUrl;
}
private string BuildConsolidatedRegex(HttpMethod method, Regex path)
{
string pathString = path.ToString().Replace("^", "");
return method.ToString() + " " + pathString;
}
}
}