aboutsummaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-05-29 17:35:08 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-05-29 17:35:08 -0400
commit765d3d328af49f92f1d0b296bfba2d7791e0cdf5 (patch)
tree4cdea3095b53f06508dbf05f4181c53e2844fc48 /plugins
parent31220eaf6583c28f2df5070c3c8841a02a17cdbe (diff)
XML file backed content routing, no more db required
Diffstat (limited to 'plugins')
-rw-r--r--plugins/VNLib.Plugins.Essentials.Content.Routing/src/IRouteStore.cs41
-rw-r--r--plugins/VNLib.Plugins.Essentials.Content.Routing/src/ManagedRouteStore.cs62
-rw-r--r--plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/DbRouteStore.cs (renamed from plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/RouteStore.cs)31
-rw-r--r--plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/Route.cs37
-rw-r--r--plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/XmlRouteStore.cs161
-rw-r--r--plugins/VNLib.Plugins.Essentials.Content.Routing/src/PageRouterEntry.cs14
-rw-r--r--plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs120
-rw-r--r--plugins/VNLib.Plugins.Essentials.Content.Routing/src/sample.routes.xml44
8 files changed, 445 insertions, 65 deletions
diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/IRouteStore.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/IRouteStore.cs
new file mode 100644
index 0000000..39af773
--- /dev/null
+++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/IRouteStore.cs
@@ -0,0 +1,41 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Content.Routing
+* File: IRouteStore.cs
+*
+* IRouteStore.cs is part of VNLib.Plugins.Essentials.Content.Routing which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Content.Routing is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Essentials.Content.Routing is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+using VNLib.Plugins.Essentials.Content.Routing.Model;
+
+namespace VNLib.Plugins.Essentials.Content.Routing
+{
+ internal interface IRouteStore
+ {
+ /// <summary>
+ /// Loads all routes from the backing storage element asynchronously
+ /// </summary>
+ /// <param name="routes">The collection to store loaded routes to</param>
+ /// <returns>A task that completes when the routes are added to the collection</returns>
+ Task GetAllRoutesAsync(ICollection<Route> routes);
+ }
+}
diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/ManagedRouteStore.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/ManagedRouteStore.cs
new file mode 100644
index 0000000..d859551
--- /dev/null
+++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/ManagedRouteStore.cs
@@ -0,0 +1,62 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Content.Routing
+* File: ManagedRouteStore.cs
+*
+* ManagedRouteStore.cs is part of VNLib.Plugins.Essentials.Content.Routing which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Content.Routing is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Essentials.Content.Routing is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+using VNLib.Plugins.Extensions.Loading;
+using VNLib.Plugins.Extensions.Loading.Sql;
+using VNLib.Plugins.Essentials.Content.Routing.Model;
+
+namespace VNLib.Plugins.Essentials.Content.Routing
+{
+ [ConfigurationName("store")]
+ internal sealed class ManagedRouteStore : IRouteStore
+ {
+ private readonly IRouteStore _routeStore;
+
+ public ManagedRouteStore(PluginBase plugin, IConfigScope config)
+ {
+ //Load managed store, see if we're using the xml file or the database
+ if(config.ContainsKey("route_file"))
+ {
+ //Load xml route store
+ _routeStore = plugin.GetOrCreateSingleton<XmlRouteStore>();
+ }
+ else
+ {
+ //Load the database backed store
+ _routeStore = plugin.GetOrCreateSingleton<DbRouteStore>();
+
+ //Ensure the database is created
+ _ = plugin.ObserveWork(() => plugin.EnsureDbCreatedAsync<RoutingContext>(plugin), 500);
+ }
+ }
+
+ public Task GetAllRoutesAsync(ICollection<Route> routes)
+ {
+ return _routeStore.GetAllRoutesAsync(routes);
+ }
+ }
+}
diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/RouteStore.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/DbRouteStore.cs
index e623228..0c2fca6 100644
--- a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/RouteStore.cs
+++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/DbRouteStore.cs
@@ -1,11 +1,11 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.Content.Routing
-* File: RouteStore.cs
+* File: DbRouteStore.cs
*
-* RouteStore.cs is part of VNLib.Plugins.Essentials.Content.Routing which is part of the larger
+* DbRouteStore.cs is part of VNLib.Plugins.Essentials.Content.Routing which is part of the larger
* VNLib collection of libraries and utilities.
*
* VNLib.Plugins.Essentials.Content.Routing is free software: you can redistribute it and/or modify
@@ -24,24 +24,37 @@
using System;
using System.Linq;
+using System.Collections.Generic;
+using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using VNLib.Plugins.Extensions.Data;
+using VNLib.Plugins.Extensions.Loading.Sql;
namespace VNLib.Plugins.Essentials.Content.Routing.Model
{
- internal class RouteStore : DbStore<Route>
+ internal class DbRouteStore : DbStore<Route>, IRouteStore
{
private readonly DbContextOptions Options;
- public RouteStore(DbContextOptions options)
+ public DbRouteStore(PluginBase plugin)
{
- Options = options;
+ //Load the db context options
+ Options = plugin.GetContextOptions();
}
+ ///<inheritdoc/>
+ public Task GetAllRoutesAsync(ICollection<Route> routes)
+ {
+ //Get all routes as a single page from the database
+ return GetPageAsync(routes, 0, int.MaxValue);
+ }
+
+ ///<inheritdoc/>
public override string RecordIdBuilder => Guid.NewGuid().ToString("N");
+ ///<inheritdoc/>
protected override IQueryable<Route> GetCollectionQueryBuilder(TransactionalDbContext context, params string[] constraints)
{
string hostname = constraints[0];
@@ -50,6 +63,7 @@ namespace VNLib.Plugins.Essentials.Content.Routing.Model
select route;
}
+ ///<inheritdoc/>
protected override IQueryable<Route> GetSingleQueryBuilder(TransactionalDbContext context, params string[] constraints)
{
string id = constraints[0];
@@ -58,11 +72,14 @@ namespace VNLib.Plugins.Essentials.Content.Routing.Model
select route;
}
+ ///<inheritdoc/>
public override TransactionalDbContext NewContext() => new RoutingContext(Options);
+ ///<inheritdoc/>
protected override void OnRecordUpdate(Route newRecord, Route currentRecord)
{
- throw new NotImplementedException();
+ throw new NotSupportedException();
}
+
}
}
diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/Route.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/Route.cs
index c1531f7..acceb0c 100644
--- a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/Route.cs
+++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/Route.cs
@@ -35,38 +35,53 @@ namespace VNLib.Plugins.Essentials.Content.Routing.Model
[Index(nameof(Id), IsUnique = true)]
internal class Route : DbModelBase
{
+ public const FpRoutine RewriteRoutine = (FpRoutine)50;
+
[Key]
public override string Id { get; set; }
public override DateTime Created { get; set; }
public override DateTime LastModified { get; set; }
public string Hostname { get; set; }
+
public string MatchPath { get; set; }
+
[Column("Privilage")]
public long _privilage
{
get => (long)Privilage;
set => Privilage = (ulong)value;
}
+
[NotMapped]
public ulong Privilage { get; set; }
- public string Alternate { get; set; }
- public FpRoutine Routine { get; set; }
+ public string? Alternate { get; set; } = string.Empty;
+
+ public FpRoutine Routine { get; set; }
+
+ public string? RewriteSearch { get; set; }
/// <summary>
- /// The processing arguments that match the route
+ /// Creates the <see cref="FileProcessArgs"/> to return to the processor
+ /// for the current rule, which may include rewriting the url.
/// </summary>
- [NotMapped]
- public FileProcessArgs MatchArgs
+ /// <param name="entity">The connection to get the args for</param>
+ /// <returns>The <see cref="FileProcessArgs"/> for the connection</returns>
+ public FileProcessArgs GetArgs(HttpEntity entity)
{
- get
+ //Check for rewrite routine
+ if (Routine == RewriteRoutine)
+ {
+ //Rewrite the request url and return the args, processor will clean and parse url
+ string rewritten = entity.Server.Path.Replace(RewriteSearch!, Alternate!, StringComparison.OrdinalIgnoreCase);
+
+ //Set to rewrite args
+ return new FileProcessArgs(FpRoutine.ServeOther, rewritten);
+ }
+ else
{
- return new FileProcessArgs()
- {
- Alternate = this.Alternate,
- Routine = (FpRoutine) Routine
- };
+ return new FileProcessArgs(Routine, Alternate!);
}
}
}
diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/XmlRouteStore.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/XmlRouteStore.cs
new file mode 100644
index 0000000..5420996
--- /dev/null
+++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/XmlRouteStore.cs
@@ -0,0 +1,161 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.Content.Routing
+* File: XmlRouteStore.cs
+*
+* XmlRouteStore.cs is part of VNLib.Plugins.Essentials.Content.Routing which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.Content.Routing is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Essentials.Content.Routing is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Xml;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+using VNLib.Utils.IO;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Logging;
+using VNLib.Utils.Extensions;
+using VNLib.Plugins.Extensions.Loading;
+
+namespace VNLib.Plugins.Essentials.Content.Routing.Model
+{
+ [ConfigurationName("store")]
+ internal sealed class XmlRouteStore : IRouteStore
+ {
+ private readonly string _routeFile;
+
+ public XmlRouteStore(PluginBase plugin, IConfigScope config)
+ {
+ //Get the route file path
+ _routeFile = config["route_file"].GetString() ?? throw new KeyNotFoundException("Missing required key 'route_file' in 'route_store' configuration element");
+
+ //Make sure the file exists
+ if (!FileOperations.FileExists(_routeFile))
+ {
+ throw new FileNotFoundException("Missing required route xml file", _routeFile);
+ }
+
+ plugin.Log.Debug("Loading routes from {0}", _routeFile);
+ }
+
+ ///<inheritdoc/>
+ public async Task GetAllRoutesAsync(ICollection<Route> routes)
+ {
+ using VnMemoryStream memStream = new();
+
+ //Load the route file
+ await using (FileStream routeFile = new(_routeFile, FileMode.Open, FileAccess.Read, FileShare.Read))
+ {
+ //Read the route file into memory
+ await routeFile.CopyToAsync(memStream, 4096, MemoryUtil.Shared, CancellationToken.None);
+ }
+
+ //Rewind the memory stream
+ memStream.Seek(0, SeekOrigin.Begin);
+
+ //Parse elements into routes
+ ParseElements(memStream, routes);
+ }
+
+ private static void ParseElements(VnMemoryStream ms, ICollection<Route> routes)
+ {
+ //Read contents into xml doc for reading
+ XmlDocument xmlDoc = new();
+ xmlDoc.Load(ms);
+
+ //Get route elements
+ XmlNodeList? routeElements = xmlDoc.SelectNodes("routes/route");
+
+ //If no route elements, exit
+ if (routeElements == null)
+ {
+ return;
+ }
+
+ foreach (XmlNode routeEl in routeElements)
+ {
+ Route route = new();
+
+ //See if route is disabled
+ string? disabledAtr = routeEl.Attributes["disabled"]?.Value;
+ //If disabled, skip route
+ if (disabledAtr != null)
+ {
+ continue;
+ }
+
+ //Get the route routine value
+ string? routineAtr = routeEl.Attributes["routine"]?.Value;
+ _ = routineAtr ?? throw new XmlException("Missing required attribute 'routine' in route element");
+
+ //Try to get the routime enum value
+ if (uint.TryParse(routineAtr, out uint r))
+ {
+ route.Routine = (FpRoutine)r;
+ }
+ else
+ {
+ throw new XmlException("The value of the 'routine' attribute is not a valid FpRoutine enum value");
+ }
+
+ //read priv level attribute
+ string? privAtr = routeEl.Attributes["privilage"]?.Value;
+ _ = privAtr ?? throw new XmlException("Missing required attribute 'priv' in route element");
+
+ //Try to get the priv level enum value
+ if (ulong.TryParse(privAtr, out ulong priv))
+ {
+ route.Privilage = priv;
+ }
+ else
+ {
+ throw new XmlException("The value of the 'priv' attribute is not a valid unsigned 64-bit integer");
+ }
+
+ //Get hostname element value
+ string? hostEl = routeEl["hostname"]?.InnerText;
+ route.Hostname = hostEl ?? throw new XmlException("Missing required element 'hostname' in route element");
+
+ //Get the path element value
+ string? pathEl = routeEl["path"]?.InnerText;
+ route.MatchPath = pathEl ?? throw new XmlException("Missing required element 'path' in route element");
+
+ //Get the optional alternate path element value
+ route.Alternate = routeEl["alternate"]?.InnerText;
+
+ //Check for rewrite routine, if rewrite, get rewrite and replace elements
+ if (route.Routine == Route.RewriteRoutine)
+ {
+ //Get the rewrite element value
+ string? rewriteEl = routeEl["rewrite"]?.InnerText;
+ route.RewriteSearch = rewriteEl ?? throw new XmlException("Missing required element 'rewrite' in route element");
+
+ //Get the rewrite element value
+ string? replaceEl = routeEl["replace"]?.InnerText;
+ route.Alternate = replaceEl ?? throw new XmlException("Missing required element 'replace' in route element");
+ }
+
+ //add route to the collection
+ routes.Add(route);
+ }
+ }
+ }
+}
diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/PageRouterEntry.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/PageRouterEntry.cs
index e71ee6b..ef7836f 100644
--- a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/PageRouterEntry.cs
+++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/PageRouterEntry.cs
@@ -23,14 +23,11 @@
*/
using System;
-using System.Threading.Tasks;
using System.ComponentModel.Design;
using VNLib.Utils.Logging;
using VNLib.Plugins.Attributes;
using VNLib.Plugins.Extensions.Loading;
-using VNLib.Plugins.Extensions.Loading.Sql;
-using VNLib.Plugins.Essentials.Content.Routing.Model;
namespace VNLib.Plugins.Essentials.Content.Routing
{
@@ -52,9 +49,6 @@ namespace VNLib.Plugins.Essentials.Content.Routing
//Init router
PageRouter = this.GetOrCreateSingleton<Router>();
- //Schedule the db creation
- _ = this.ObserveWork(OnDbCreationAsync, 500);
-
Log.Information("Plugin loaded");
}
@@ -70,12 +64,6 @@ namespace VNLib.Plugins.Essentials.Content.Routing
PageRouter?.ResetRoutes();
Log.Information("Routing table reset");
}
- }
-
- private async Task OnDbCreationAsync()
- {
- //Create the router
- await this.EnsureDbCreatedAsync<RoutingContext>(null);
- }
+ }
}
}
diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs
index fdf5f59..3d3a1a6 100644
--- a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs
+++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs
@@ -30,40 +30,40 @@ using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
+using VNLib.Utils.Logging;
+using VNLib.Utils.Extensions;
using VNLib.Plugins.Essentials.Accounts;
using VNLib.Plugins.Extensions.Loading;
-using VNLib.Plugins.Extensions.Loading.Sql;
using VNLib.Plugins.Essentials.Content.Routing.Model;
-using static VNLib.Plugins.Essentials.Accounts.AccountUtil;
namespace VNLib.Plugins.Essentials.Content.Routing
{
- [ConfigurationName("page_router", Required = false)]
+
internal class Router : IPageRouter
{
private static readonly RouteComparer Comparer = new();
- private readonly RouteStore Store;
+ private readonly IRouteStore Store;
+ private readonly ILogProvider Logger;
private readonly ConcurrentDictionary<IWebProcessor, Task<ReadOnlyCollection<Route>>> RouteTable;
public Router(PluginBase plugin)
{
- Store = new(plugin.GetContextOptions());
+ Store = plugin.GetOrCreateSingleton<ManagedRouteStore>();
+ Logger = plugin.Log;
RouteTable = new();
}
- public Router(PluginBase plugin, IConfigScope config)
- {
- Store = new(plugin.GetContextOptions());
- RouteTable = new();
- }
+ public Router(PluginBase plugin, IConfigScope config):this(plugin)
+ { }
///<inheritdoc/>
public async ValueTask<FileProcessArgs> RouteAsync(HttpEntity entity)
{
- ulong privilage = READ_MSK;
+ //Default to read-only privilages
+ ulong privilage = AccountUtil.READ_MSK;
//Only select privilages for logged-in users, this is a medium security check since we may not have all data available
if (entity.Session.IsSet && entity.IsClientAuthorized(AuthorzationCheckLevel.Medium))
@@ -73,8 +73,12 @@ namespace VNLib.Plugins.Essentials.Content.Routing
//Get the routing table for the current host
ReadOnlyCollection<Route> routes = await RouteTable.GetOrAdd(entity.RequestedRoot, LoadRoutesAsync);
+
//Find the proper routine for the connection
- return FindArgs(routes, entity.RequestedRoot.Hostname, entity.Server.Path, privilage);
+ Route? selected = SelectBestRoute(routes, entity.RequestedRoot.Hostname, entity.Server.Path, privilage);
+
+ //Get the arguments for the selected route, if not found allow the connection to continue
+ return selected?.GetArgs(entity) ?? FileProcessArgs.Continue;
}
/// <summary>
@@ -82,11 +86,16 @@ namespace VNLib.Plugins.Essentials.Content.Routing
/// </summary>
public void ResetRoutes() => RouteTable.Clear();
+
private async Task<ReadOnlyCollection<Route>> LoadRoutesAsync(IWebProcessor root)
{
List<Route> collection = new();
- //Load all routes
- _ = await Store.GetPageAsync(collection, 0, int.MaxValue);
+
+ //Load all routes from the backing store and filter them
+ await Store.GetAllRoutesAsync(collection);
+
+ Logger.Debug("Found {r} routes in store", collection.Count);
+
//Select only exact match routes, or wildcard routes
return (from r in collection
where r.Hostname.EndsWith(root.Hostname, StringComparison.OrdinalIgnoreCase) || r.Hostname == "*"
@@ -97,37 +106,67 @@ namespace VNLib.Plugins.Essentials.Content.Routing
.AsReadOnly();
}
-
- private static FileProcessArgs FindArgs(ReadOnlyCollection<Route> routes, string hostname, string path, ulong privilages)
+ /// <summary>
+ /// Selects the best route for a given hostname, path, and privilage level and returns it
+ /// if one could be found
+ /// </summary>
+ /// <param name="routes">The routes collection to read</param>
+ /// <param name="hostname">The connection hostname to filter routes for</param>
+ /// <param name="path">The connection url path to filter routes for</param>
+ /// <param name="privilages">The calculated privialges of the connection</param>
+ /// <returns>The best route match for the connection if one is found, null otherwise</returns>
+ private static Route? SelectBestRoute(ReadOnlyCollection<Route> routes, string hostname, string path, ulong privilages)
{
//Rent an array to sort routes for the current user
Route[] matchArray = ArrayPool<Route>.Shared.Rent(routes.Count);
int count = 0;
+
//Search for routes that match
- for(int i = 0; i < routes.Count; i++)
+ for (int i = 0; i < routes.Count; i++)
{
- if(Matches(routes[i], hostname, path, privilages))
+ if (FastMatch(routes[i], hostname, path, privilages))
{
//Add to sort array
matchArray[count++] = routes[i];
}
}
+
//If no matches are found, return continue routine
if (count == 0)
{
//Return the array to the pool
ArrayPool<Route>.Shared.Return(matchArray);
- return FileProcessArgs.Continue;
+ return null;
+ }
+
+ //If only one match is found, return it
+ if (count == 1)
+ {
+ //Return the array to the pool
+ ArrayPool<Route>.Shared.Return(matchArray);
+ return matchArray[0];
+ }
+ else
+ {
+ //Get sorting span for matches
+ Span<Route> found = matchArray.AsSpan(0, count);
+
+ /*
+ * Sortining elements using the static comparer, to find the best match
+ * out of all matching routes.
+ *
+ * The comparer will put the most specific routes at the end of the array
+ */
+ found.Sort(Comparer);
+
+ //Select the last element
+ Route selected = found[^1];
+
+ //Return array to pool
+ ArrayPool<Route>.Shared.Return(matchArray);
+
+ return selected;
}
- //Get sorting span for matches
- Span<Route> found = matchArray.AsSpan(0, count);
- //Sort the found rules
- found.Sort(Comparer);
- //Select the last element
- Route selected = found[^1];
- //Return array to pool
- ArrayPool<Route>.Shared.Return(matchArray);
- return selected.MatchArgs;
}
/// <summary>
@@ -139,25 +178,38 @@ namespace VNLib.Plugins.Essentials.Content.Routing
/// <param name="path">The resource path to test</param>
/// <param name="privilages">The privialge level to search for</param>
/// <returns>True if the route can be matched to the resource and the privialge level</returns>
- private static bool Matches(Route route, ReadOnlySpan<char> hostname, ReadOnlySpan<char> path, ulong privilages)
+ private static bool FastMatch(Route route, ReadOnlySpan<char> hostname, ReadOnlySpan<char> path, ulong privilages)
{
//Get span of hostname to stop string heap allocations during comparisons
ReadOnlySpan<char> routineHost = route.Hostname;
ReadOnlySpan<char> routinePath = route.MatchPath;
- //Test if hostname hostname matches exactly (may be wildcard) or hostname begins with a wildcard and ends with the request hostname
- bool hostMatch = routineHost.SequenceEqual(hostname) || (routineHost.Length > 1 && routineHost[0] == '*' && hostname.EndsWith(routineHost[1..]));
+
+ //Test if hostname matches
+ bool hostMatch =
+ //Wildcard routine only, matches all hostnames
+ (routineHost.Length == 1 && routineHost[0] == '*')
+ //Exact hostname match
+ || routineHost.SequenceEqual(hostname)
+ //wildcard hostname match with trailing
+ || (routineHost.Length > 1 && routineHost[0] == '*' && hostname.EndsWith(routineHost[1..], StringComparison.OrdinalIgnoreCase));
+
if (!hostMatch)
{
return false;
}
- //Test if path is a wildcard, matches exactly, or if the path is a wildcard path, that the begining of the reqest path matches the routine path
- bool pathMatch = routinePath == "*" || routinePath.SequenceEqual(path) || (routinePath.Length > 1 && routinePath[^1] == '*' && path.StartsWith(routinePath[..^1]));
+
+ //Test if path is a wildcard, matches exactly, or if the path is a wildcard path, that the begining of the request path matches the routine path
+ bool pathMatch = routinePath == "*"
+ || routinePath.Equals(path, StringComparison.OrdinalIgnoreCase)
+ || (routinePath.Length > 1 && routinePath[^1] == '*' && path.StartsWith(routinePath[..^1], StringComparison.OrdinalIgnoreCase));
+
if (!pathMatch)
{
return false;
}
+
//Test if the level and group privilages match for the current routine
- return (privilages & LEVEL_MSK) >= (route.Privilage & LEVEL_MSK) && (route.Privilage & GROUP_MSK) == (privilages & GROUP_MSK);
+ return (privilages & AccountUtil.LEVEL_MSK) >= (route.Privilage & AccountUtil.LEVEL_MSK) && (route.Privilage & AccountUtil.GROUP_MSK) == (privilages & AccountUtil.GROUP_MSK);
}
}
}
diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/sample.routes.xml b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/sample.routes.xml
new file mode 100644
index 0000000..3c87aa7
--- /dev/null
+++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/sample.routes.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8" ?>
+
+<!--Routes container element holds a collection of route elements-->
+<routes>
+ <!--
+ Example route configuration for a single page app
+ where the tree looks like this:
+ / (index.html)
+ /assets (assets directory) (css and js files)
+
+ Wildcard hosts match all hosts that do not have rules with more specific hosts
+ -->
+
+ <!--
+ Allow assets directory to pass through for all requests, using the Continue routine (1)
+
+ Because this route has a more specific path than the catch all route
+ it will be processed first
+ -->
+ <route routine="1" privilage="0">
+
+ <!--Wildcard host-->
+ <hostname>*</hostname>
+
+ <!--All paths that start with /assets/ will be matched-->
+ <path>/assets/*</path>
+ </route>
+
+ <!--Overwrite all other requests to the index file (catch all) using the ServeOther routine (4)-->
+ <route routine="4" privilage="0">
+
+ <!--Wildcard hostname-->
+ <hostname>*</hostname>
+
+ <!--Declares that all files after / will be matched by this rule-->
+ <path>/*</path>
+
+ <!--Return to the root path, lets the file processor handle extension searching-->
+ <alternate>/</alternate>
+ </route>
+
+ <!--All routes that do not match will be allowed, this is only / since it does not have a matching rule-->
+
+</routes> \ No newline at end of file