diff options
author | vnugent <public@vaughnnugent.com> | 2023-01-12 17:47:40 -0500 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-01-12 17:47:40 -0500 |
commit | 551066ed9a255bd47c1c5789ec1998fda64bd5aa (patch) | |
tree | d6caceb0e7caa44478c6611903b4b7e120964c89 /plugins/VNLib.Plugins.Essentials.Content.Routing/src | |
parent | b6481038bc6573af30492e9ce52b36d9f64195f3 (diff) |
Large project reorder and consolidation
Diffstat (limited to 'plugins/VNLib.Plugins.Essentials.Content.Routing/src')
7 files changed, 541 insertions, 0 deletions
diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/Route.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/Route.cs new file mode 100644 index 0000000..8c52725 --- /dev/null +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/Route.cs @@ -0,0 +1,71 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.Content.Routing +* File: Route.cs +* +* Route.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.ComponentModel.DataAnnotations.Schema; + +using Microsoft.EntityFrameworkCore; + +using VNLib.Plugins.Extensions.Data; + +namespace VNLib.Plugins.Essentials.Content.Routing.Model +{ + [Index(nameof(Id), IsUnique = true)] + internal class Route : DbModelBase + { + 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; } + + /// <summary> + /// The processing arguments that match the route + /// </summary> + [NotMapped] + public FileProcessArgs MatchArgs + { + get + { + return new FileProcessArgs() + { + Alternate = this.Alternate, + Routine = (FpRoutine) Routine + }; + } + } + } +} diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/RouteStore.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/RouteStore.cs new file mode 100644 index 0000000..e623228 --- /dev/null +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/RouteStore.cs @@ -0,0 +1,68 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.Content.Routing +* File: RouteStore.cs +* +* RouteStore.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.Linq; + +using Microsoft.EntityFrameworkCore; + +using VNLib.Plugins.Extensions.Data; + +namespace VNLib.Plugins.Essentials.Content.Routing.Model +{ + internal class RouteStore : DbStore<Route> + { + private readonly DbContextOptions Options; + + public RouteStore(DbContextOptions options) + { + Options = options; + } + + public override string RecordIdBuilder => Guid.NewGuid().ToString("N"); + + protected override IQueryable<Route> GetCollectionQueryBuilder(TransactionalDbContext context, params string[] constraints) + { + string hostname = constraints[0]; + return from route in context.Set<Route>() + where route.Hostname == hostname + select route; + } + + protected override IQueryable<Route> GetSingleQueryBuilder(TransactionalDbContext context, params string[] constraints) + { + string id = constraints[0]; + return from route in context.Set<Route>() + where route.Id == id + select route; + } + + public override TransactionalDbContext NewContext() => new RoutingContext(Options); + + protected override void OnRecordUpdate(Route newRecord, Route currentRecord) + { + throw new NotImplementedException(); + } + } +} diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/RoutingContext.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/RoutingContext.cs new file mode 100644 index 0000000..0cbd90f --- /dev/null +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/RoutingContext.cs @@ -0,0 +1,41 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.Content.Routing +* File: RoutingContext.cs +* +* RoutingContext.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 Microsoft.EntityFrameworkCore; + +using VNLib.Plugins.Extensions.Data; + +namespace VNLib.Plugins.Essentials.Content.Routing.Model +{ + internal class RoutingContext : TransactionalDbContext + { + public DbSet<Route> Routes { get; set; } + + public RoutingContext(DbContextOptions options) :base(options) + { + } + } +} diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/PageRouterEntry.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/PageRouterEntry.cs new file mode 100644 index 0000000..10b7075 --- /dev/null +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/PageRouterEntry.cs @@ -0,0 +1,68 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.Content.Routing +* File: PageRouterEntry.cs +* +* PageRouterEntry.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.Threading.Tasks; +using System.Collections.Generic; + +using VNLib.Utils.Logging; + +namespace VNLib.Plugins.Essentials.Content.Routing +{ + public sealed class PageRouterEntry : PluginBase, IPageRouter + { + public override string PluginName => "Essentials.Router"; + + private Router PageRouter; + public ValueTask<FileProcessArgs> RouteAsync(HttpEntity entity) => PageRouter.RouteAsync(entity); + + protected override void OnLoad() + { + try + { + //Init router + PageRouter = new(this); + Log.Information("Plugin loaded"); + } + catch (KeyNotFoundException knf) + { + Log.Error("Plugin failed to load, missing required configuration variables {err}", knf.Message); + } + } + + protected override void OnUnLoad() + { + Log.Information("Plugin unloaded"); + } + + protected override void ProcessHostCommand(string cmd) + { + if(cmd.Contains("reset", StringComparison.OrdinalIgnoreCase)) + { + PageRouter?.ResetRoutes(); + Log.Information("Routing table reset"); + } + } + } +} diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/RouteComparer.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/RouteComparer.cs new file mode 100644 index 0000000..189da62 --- /dev/null +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/RouteComparer.cs @@ -0,0 +1,77 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.Content.Routing +* File: RouteComparer.cs +* +* RouteComparer.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.Collections.Generic; + +using VNLib.Plugins.Essentials.Content.Routing.Model; + +using static VNLib.Plugins.Essentials.Accounts.AccountManager; + +namespace VNLib.Plugins.Essentials.Content.Routing +{ + /// <summary> + /// Sorts routing rules based on closest match path/hostname routing along with privilage priority + /// </summary> + internal class RouteComparer : IComparer<Route> + { + //The idea is that hostnames without wildcards are exact, and hostnames with wildcards are "catch all" + public int Compare(Route x, Route y) + { + int val = 0; + //If x contains a wildcard in the hostname, then it is less than y + if (x.Hostname.Contains('*')) + { + val--; + } + //If y containts a wildcard, then y is less than x + if (y.Hostname.Contains('*')) + { + val++; + } + //If there was no wildcard, check paths + if (val == 0) + { + //If x containts a wildcard in the path, then x is less than y + if (x.MatchPath.Contains('*')) + { + val--; + } + //If y containts a wildcard in the path, then y is less than x + if (y.MatchPath.Contains('*')) + { + val++; + + } + } + //If hostnames and paths are stil equal, check privilage level + if (val == 0) + { + //Higher privilage routine is greater than lower privilage + val = (x.Privilage & LEVEL_MSK) > (y.Privilage & LEVEL_MSK) ? 1 : -1; + } + //If both contain (or are) wildcards, then they are equal + return val; + } + } +} diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs new file mode 100644 index 0000000..4dc320a --- /dev/null +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs @@ -0,0 +1,161 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.Content.Routing +* File: Router.cs +* +* Router.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.Linq; +using System.Buffers; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Collections.ObjectModel; + +using VNLib.Net.Http; +using VNLib.Utils.Logging; +using VNLib.Plugins.Extensions.Loading.Sql; +using VNLib.Plugins.Extensions.Loading.Events; +using VNLib.Plugins.Essentials.Content.Routing.Model; +using static VNLib.Plugins.Essentials.Accounts.AccountManager; + +namespace VNLib.Plugins.Essentials.Content.Routing +{ + internal class Router : IPageRouter, IIntervalScheduleable + { + private static readonly RouteComparer Comparer = new(); + + private readonly RouteStore Store; + + private readonly ConcurrentDictionary<IWebRoot, Task<ReadOnlyCollection<Route>>> RouteTable; + + public Router(PluginBase plugin) + { + Store = new(plugin.GetContextOptions()); + plugin.ScheduleInterval(this, TimeSpan.FromSeconds(30)); + RouteTable = new(); + } + + ///<inheritdoc/> + public async ValueTask<FileProcessArgs> RouteAsync(HttpEntity entity) + { + ulong privilage = READ_MSK; + //Only select privilages for logged-in users + if (entity.Session.IsSet && entity.LoginCookieMatches() || entity.TokenMatches()) + { + privilage = entity.Session.Privilages; + } + //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); + } + + /// <summary> + /// Clears all cached routines from the database + /// </summary> + public void ResetRoutes() => RouteTable.Clear(); + + private async Task<ReadOnlyCollection<Route>> LoadRoutesAsync(IWebRoot root) + { + List<Route> collection = new(); + //Load all routes + _ = await Store.GetPageAsync(collection, 0, int.MaxValue); + //Select only exact match routes, or wildcard routes + return (from r in collection + where r.Hostname.EndsWith(root.Hostname, StringComparison.OrdinalIgnoreCase) || r.Hostname == "*" + //Orderby path "specificity" longer pathts are generally more specific, so filter order + orderby r.MatchPath.Length ascending + select r) + .ToList() + .AsReadOnly(); + } + + + private static FileProcessArgs FindArgs(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++) + { + if(Matches(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; + } + //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> + /// Determines if a route can be matched to a hostname, resource path, and a + /// privilage level + /// </summary> + /// <param name="route">The route to test against</param> + /// <param name="hostname">The hostname to test</param> + /// <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) + { + //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..])); + 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])); + 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); + } + + Task IIntervalScheduleable.OnIntervalAsync(ILogProvider log, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/VNLib.Plugins.Essentials.Content.Routing.csproj b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/VNLib.Plugins.Essentials.Content.Routing.csproj new file mode 100644 index 0000000..7052da0 --- /dev/null +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/VNLib.Plugins.Essentials.Content.Routing.csproj @@ -0,0 +1,55 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <Authors>Vaughn Nugent</Authors> + <Version>1.0.1.1</Version> + <Copyright>Copyright © 2022 Vaughn Nugent</Copyright> + <PackageProjectUrl>https://www.vaughnnugent.com</PackageProjectUrl> + <AssemblyName>PageRouter</AssemblyName> + <SignAssembly>True</SignAssembly> + <AssemblyOriginatorKeyFile>\\vaughnnugent.com\Internal\Folder Redirection\vman\Documents\Programming\Software\StrongNameingKey.snk</AssemblyOriginatorKeyFile> + </PropertyGroup> + + <!-- Resolve nuget dll files and store them in the output dir --> + <PropertyGroup> + <!--Enable dynamic loading--> + <EnableDynamicLoading>true</EnableDynamicLoading> + <Nullable>enable</Nullable> + <AnalysisLevel>latest-all</AnalysisLevel> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> + <Deterministic>False</Deterministic> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> + <Deterministic>False</Deterministic> + </PropertyGroup> + <ItemGroup> + <PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="ErrorProne.NET.Structs" Version="0.1.2"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\..\..\..\core\lib\Plugins.PluginBase\src\VNLib.Plugins.PluginBase.csproj" /> + <ProjectReference Include="..\..\..\..\Extensions\lib\VNLib.Plugins.Extensions.Data\src\VNLib.Plugins.Extensions.Data.csproj" /> + <ProjectReference Include="..\..\..\..\Extensions\lib\VNLib.Plugins.Extensions.Loading.Sql\src\VNLib.Plugins.Extensions.Loading.Sql.csproj" /> + <ProjectReference Include="..\..\..\..\Extensions\lib\VNLib.Plugins.Extensions.Loading\src\VNLib.Plugins.Extensions.Loading.csproj" /> + </ItemGroup> + + <ItemGroup> + <None Update="PageRouter.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + </ItemGroup> + + <Target Name="PostBuild" AfterTargets="PostBuildEvent"> + <Exec Command="start xcopy "$(TargetDir)" "F:\Programming\vnlib\devplugins\$(TargetName)" /E /Y /R" /> + </Target> + +</Project> |