From 711b12fa249cba9effecd4e722dd8d460d083659 Mon Sep 17 00:00:00 2001 From: vnugent Date: Tue, 17 Sep 2024 14:29:49 -0400 Subject: feat: some mvc static routing extensions --- .../src/ConfigurationExtensions.cs | 107 ++++--- .../src/LoadingExtensions.cs | 13 +- .../src/Routing/Mvc/HttpControllerAttribute.cs | 36 --- .../src/Routing/Mvc/HttpEndpointAttribute.cs | 52 ---- .../Routing/Mvc/HttpRouteProtectionAttribute.cs | 63 ++++ .../src/Routing/Mvc/HttpStaticRouteAttribute.cs | 53 ++++ .../src/Routing/Mvc/IHttpController.cs | 52 ++++ .../src/Routing/Mvc/MvcExtensions.cs | 329 +++++++++++++++++++++ .../src/Routing/RoutingExtensions.cs | 92 ++---- .../src/Secrets/VaultSecrets.cs | 15 - 10 files changed, 597 insertions(+), 215 deletions(-) delete mode 100644 lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpControllerAttribute.cs delete mode 100644 lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpEndpointAttribute.cs create mode 100644 lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpRouteProtectionAttribute.cs create mode 100644 lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpStaticRouteAttribute.cs create mode 100644 lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/IHttpController.cs create mode 100644 lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/MvcExtensions.cs diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs index 30711fa..149ab29 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs @@ -69,37 +69,6 @@ namespace VNLib.Plugins.Extensions.Loading public const string PLUGIN_ASSET_KEY = "assets"; public const string PLUGINS_HOST_KEY = "plugins"; - /// - /// Retrieves a top level configuration dictionary of elements for the specified type. - /// The type must contain a - /// - /// The type to get the configuration of - /// - /// A of top level configuration elements for the type - /// - /// - public static IConfigScope GetConfigForType(this PluginBase plugin) - { - Type t = typeof(T); - return plugin.GetConfigForType(t); - } - - /// - /// Retrieves a top level configuration dictionary of elements with the specified property name. - /// - /// - /// Search order: Plugin config, fall back to host config, throw if not found - /// - /// - /// The config property name to retrieve - /// A of top level configuration elements for the type - /// - /// - public static IConfigScope GetConfig(this PluginBase plugin, string propName) - { - return TryGetConfig(plugin, propName) - ?? throw new ConfigurationException($"Missing required top level configuration object '{propName}', in host/plugin configuration files"); - } /// /// Retrieves a top level configuration dictionary of elements with the specified property name, @@ -116,7 +85,7 @@ namespace VNLib.Plugins.Extensions.Loading { plugin.ThrowIfUnloaded(); //Try to get the element from the plugin config first, or fallback to host - if (plugin.PluginConfig.TryGetProperty(propName, out JsonElement el) + if (plugin.PluginConfig.TryGetProperty(propName, out JsonElement el) || plugin.HostConfig.TryGetProperty(propName, out el)) { //Get the top level config as a dictionary @@ -131,22 +100,80 @@ namespace VNLib.Plugins.Extensions.Loading /// The type must contain a /// /// - /// The type to get configuration data for - /// A of top level configuration elements for the type + /// The class type to get the configuration scope variable from + /// A for the desired top-level configuration scope + /// /// - public static IConfigScope GetConfigForType(this PluginBase plugin, Type type) + public static IConfigScope? TryGetConfigForType(this PluginBase plugin, Type type) { ArgumentNullException.ThrowIfNull(type); string? configName = GetConfigNameForType(type); - if (configName == null) - { - ThrowConfigNotFoundForType(type); - } + return configName != null + ? TryGetConfig(plugin, configName) + : null; + } - return plugin.GetConfig(configName); + /// + /// Retrieves a top level configuration dictionary of elements for the specified type. + /// The type must contain a + /// + /// The type to get the configuration of + /// + /// A of top level configuration elements for the type + /// + /// + public static IConfigScope? TryGetConfigForType(this PluginBase plugin) + { + return TryGetConfigForType(plugin, typeof(T)); + } + + /// + /// Retrieves a top level configuration dictionary of elements for the specified type. + /// The type must contain a + /// + /// + /// The type to get configuration data for + /// A of top level configuration elements for the type + /// + public static IConfigScope GetConfigForType(this PluginBase plugin, Type type) + { + return TryGetConfigForType(plugin, type) + ?? throw new ConfigurationException($"Missing required configuration key for type {type.Name}"); + } + + /// + /// Retrieves a top level configuration dictionary of elements for the specified type. + /// The type must contain a + /// + /// The type to get the configuration of + /// + /// A of top level configuration elements for the type + /// + /// + public static IConfigScope GetConfigForType(this PluginBase plugin) + { + return GetConfigForType(plugin, typeof(T)); + } + + /// + /// Retrieves a top level configuration dictionary of elements with the specified property name. + /// + /// + /// Search order: Plugin config, fall back to host config, throw if not found + /// + /// + /// The config property name to retrieve + /// A of top level configuration elements for the type + /// + /// + public static IConfigScope GetConfig(this PluginBase plugin, string propName) + { + return TryGetConfig(plugin, propName) + ?? throw new ConfigurationException($"Missing required top level configuration object '{propName}', in host/plugin configuration files"); } + /// /// Gets a required configuration property from the specified configuration scope diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs index ca897e6..b3fa7b7 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs @@ -552,15 +552,10 @@ namespace VNLib.Plugins.Extensions.Loading /// public static T CreateService(this PluginBase plugin) { - if (plugin.HasConfigForType()) - { - IConfigScope config = plugin.GetConfigForType(); - return CreateService(plugin, config); - } - else - { - return CreateService(plugin, (IConfigScope?)null); - } + return CreateService( + plugin, + config: plugin.TryGetConfigForType() + ); } /// diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpControllerAttribute.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpControllerAttribute.cs deleted file mode 100644 index 94771ab..0000000 --- a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpControllerAttribute.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* -* Copyright (c) 2024 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Extensions.Loading -* File: HttpControllerAttribute.cs -* -* HttpControllerAttribute.cs is part of VNLib.Plugins.Extensions.Loading which is -* part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Extensions.Loading 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.Extensions.Loading 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; - -namespace VNLib.Plugins.Extensions.Loading.Routing.Mvc -{ - /// - /// Attribute to define a controller for http routing. The class must be decorated - /// with this attribute to be recognized as a controller - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public sealed class HttpControllerAttribute : Attribute - { } -} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpEndpointAttribute.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpEndpointAttribute.cs deleted file mode 100644 index d89df63..0000000 --- a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpEndpointAttribute.cs +++ /dev/null @@ -1,52 +0,0 @@ -/* -* Copyright (c) 2024 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Extensions.Loading -* File: HttpEndpointAttribute.cs -* -* HttpEndpointAttribute.cs is part of VNLib.Plugins.Extensions.Loading which is -* part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Extensions.Loading 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.Extensions.Loading 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 VNLib.Net.Http; - -namespace VNLib.Plugins.Extensions.Loading.Routing.Mvc -{ - - /// - /// Attribute to define an http endpoint for a controller. The class - /// must be decorated with the attribute - /// - /// The endpoint path - /// The method (or methods) allowed to be filtered by this endpoint - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public sealed class HttpEndpointAttribute(string path, HttpMethod method) : Attribute - { - /// - /// The path of the endpoint - /// - public string Path { get; } = path; - - /// - /// The http method of the endpoint. You may set more than one method - /// for a given endpoint - /// - public HttpMethod Method { get; } = method; - } -} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpRouteProtectionAttribute.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpRouteProtectionAttribute.cs new file mode 100644 index 0000000..d0a281c --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpRouteProtectionAttribute.cs @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Extensions.Loading +* File: HttpRouteProtectionAttribute.cs +* +* HttpRouteProtectionAttribute.cs is part of VNLib.Plugins.Extensions.Loading which is +* part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Extensions.Loading 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.Extensions.Loading 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.Net; + +using VNLib.Plugins.Essentials.Accounts; +using VNLib.Plugins.Essentials.Sessions; + +namespace VNLib.Plugins.Extensions.Loading.Routing.Mvc +{ + /// + /// When applied to a method, this attribute will require the client to have a valid + /// authorization in order to access the endpoint. + /// + /// The protection authorization level + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class HttpRouteProtectionAttribute(AuthorzationCheckLevel authLevel) : Attribute + { + /// + /// Defines the allowed session types for this endpoint + /// + public SessionType SessionType { get; init; } = SessionType.Web; + + /// + /// The minimum authorization level required to access the endpoint + /// + public AuthorzationCheckLevel AuthLevel { get; } = authLevel; + + /// + /// The status code to return when the client is not authorized + /// + public HttpStatusCode ErrorCode { get; init; } = HttpStatusCode.Unauthorized; + + /// + /// If true allows connections with newly initalized sessions. This is a protection + /// because allowing new sessions allows connections with ensuring the same + /// session has been reused and verified. + /// + public bool AllowNewSession { get; init; } + } +} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpStaticRouteAttribute.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpStaticRouteAttribute.cs new file mode 100644 index 0000000..e37c50b --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpStaticRouteAttribute.cs @@ -0,0 +1,53 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Extensions.Loading +* File: HttpStaticRouteAttribute.cs +* +* HttpStaticRouteAttribute.cs is part of VNLib.Plugins.Extensions.Loading which is +* part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Extensions.Loading 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.Extensions.Loading 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 VNLib.Net.Http; + +namespace VNLib.Plugins.Extensions.Loading.Routing.Mvc +{ + + /// + /// Attribute to define a static http endpoint for a controller. A static route is a + /// route that is configured at startup and does not do any type of dynamic pattern + /// matching. + /// + /// The static route path, may include configuration substitution variables + /// The method (or methods) allowed to be filtered by this endpoint + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class HttpStaticRouteAttribute(string path, HttpMethod method) : Attribute + { + /// + /// The path of the endpoint + /// + public string Path { get; } = path; + + /// + /// The http method of the endpoint. You may set more than one method + /// for a given endpoint + /// + public HttpMethod Method { get; } = method; + } +} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/IHttpController.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/IHttpController.cs new file mode 100644 index 0000000..18c5bbc --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/IHttpController.cs @@ -0,0 +1,52 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Extensions.Loading +* File: IHttpController.cs +* +* IHttpController.cs is part of VNLib.Plugins.Extensions.Loading which is +* part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Extensions.Loading 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.Extensions.Loading 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 VNLib.Plugins.Essentials; +using VNLib.Plugins.Essentials.Endpoints; + +namespace VNLib.Plugins.Extensions.Loading.Routing.Mvc +{ + /// + /// The base interface type for all http controllers, which + /// are responsible for handling http requests. + /// + public interface IHttpController + { + /// + /// Gets the protection settings for all routes within + /// this controller. + /// + /// The endpoint protection settings for all routes + ProtectionSettings GetProtectionSettings(); + + /// + /// Allows pre-processing of the http entity before + /// the request is processed by routing handlers + /// + /// The request entity to pre-process + /// A value that indicates if the request should continue processing or return + virtual bool PreProccess(HttpEntity entity) => true; + } + +} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/MvcExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/MvcExtensions.cs new file mode 100644 index 0000000..428972a --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/MvcExtensions.cs @@ -0,0 +1,329 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Extensions.Loading +* File: RoutingExtensions.cs +* +* RoutingExtensions.cs is part of VNLib.Plugins.Extensions.Loading which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Extensions.Loading 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.Extensions.Loading 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.Net; +using System.Numerics; +using System.Diagnostics; +using System.Reflection; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +using VNLib.Net.Http; +using VNLib.Utils; +using VNLib.Utils.Logging; +using VNLib.Plugins.Essentials; +using VNLib.Plugins.Essentials.Accounts; +using VNLib.Plugins.Essentials.Endpoints; +using VNLib.Plugins.Essentials.Sessions; + + +namespace VNLib.Plugins.Extensions.Loading.Routing.Mvc +{ + /// + /// Provides extension and helper classes for routing using MVC architecture + /// + public static class MvcExtensions + { + /// + /// Routes all endpoints for the specified controller + /// + /// + /// The controller instance to route endpoints for + /// + /// + public static T Route(this PluginBase plugin, T? controller) where T : IHttpController + { + //If a null controller is passed (normal case) then create a new instance + controller ??= plugin.CreateService(); + + IEndpoint[] staticEndpoints = GetStaticEndpointsForController(plugin, controller); + + Array.ForEach(staticEndpoints, plugin.Route); + + return controller; + } + + /// + /// Routes all endpoints for the specified controller + /// + /// + /// + /// + public static T Route(this PluginBase plugin) where T : IHttpController + => plugin.Route(default(T)); + + private static IEndpoint[] GetStaticEndpointsForController(PluginBase plugin, T controller) + where T : IHttpController + { + IConfigScope? config = plugin.TryGetConfigForType(); + ILogProvider logger = RoutingExtensions.ConfigureLogger(plugin, config); + + StaticRouteHandler[] staticRoutes = GetStaticRoutes(controller, config); + + if(plugin.IsDebug()) + { + (string, string, string)[] eps = staticRoutes + .Select(static p => (p.Path, p.Route.Method.ToString(), p.WorkFunc.GetMethodInfo().Name)) + .ToArray(); + + plugin.Log.Verbose("Routing static endpoints: {eps}", eps); + } + + return BuildStaticRoutes(controller, logger, staticRoutes); + } + + + private static StaticRouteHandler[] GetStaticRoutes(T controller, IConfigScope? config) + where T : IHttpController + { + List routes = []; + + foreach (MethodInfo method in typeof(T).GetMethods()) + { + HttpStaticRouteAttribute? route = method.GetCustomAttribute(); + HttpRouteProtectionAttribute? protection = method.GetCustomAttribute(); + + if (route is null) + { + continue; + } + + routes.Add(new StaticRouteHandler + { + Parent = controller, + Route = route, + Protection = HttpProtectionHandler.Create(protection), + Path = RoutingExtensions.SubsituteConfigStringValue(route.Path, config), //Path may have config variables to substitute + WorkFunc = CreateWorkFunc(controller, method) //Extract the processor delegate from the method + }); + } + + return [.. routes]; + + static EndpointWorkFunc CreateWorkFunc(T controller, MethodInfo method) + { + //Create the delegate for the method + EndpointWorkFunc? del = method.CreateDelegate(controller); + + return del ?? throw new InvalidOperationException($"Failed to create delegate for method {method.Name}"); + } + } + + private static StaticEndpoint[] BuildStaticRoutes(IHttpController parent, ILogProvider logger, StaticRouteHandler[] routes) + { + //Group routes with the same path together + IEnumerable groups = routes + .GroupBy(static p => p.Path) + .Select(static p => new RoutesWithSamePathGroup(p.Key, [.. p])); + + //Get endpoints for all groups that share the same endpoint path + return groups + .Select(i => new StaticEndpoint(i.Routes, logger, parent, i.Path)) + .ToArray(); + } + + /* + * A static endpoint maps functions from within http controllres labeled + * with the HttpStaticRouteAttribute to the IEndpoint interface that vnlib + * needs to process virtual connections. + * + * This is an abstraction for architecture mapping. This endpoint will serve + * a single path, but can server mutliple http methods. + */ + private sealed class StaticEndpoint(IHttpController parent) : ResourceEndpointBase + { + /* + * This array holds all the processor functions for each http method. + * + * The array size is fixed for performance reasons, and for future compatibility + * between the http library and this one. 32 positions shouldn't be that + * much memory to worry about as the handlers are reference types. + */ + private readonly StaticRouteProcessor[] _processorFunctions = new StaticRouteProcessor[32]; + + //Cache local copy incase the parent call creates too much overhead + private readonly ProtectionSettings _protection = parent.GetProtectionSettings(); + + /// + /// + /// + protected override ProtectionSettings EndpointProtectionSettings => _protection; + + internal StaticEndpoint( + StaticRouteHandler[] routes, + ILogProvider logger, + IHttpController parent, + string staticRoutePath + ) + : this(parent) + { + //Ensure all routes have the same path, this is a developer error + foreach (StaticRouteHandler route in routes) + { + Debug.Assert(string.Equals(route.Path, staticRoutePath, StringComparison.OrdinalIgnoreCase)); + } + + InitPathAndLog(staticRoutePath, logger); + + InitProcessors(routes, _processorFunctions); + } + + /// + protected override ERRNO PreProccess(HttpEntity entity) + { + return base.PreProccess(entity) && parent.PreProccess(entity); + } + + /// + protected override ValueTask OnProcessAsync(HttpEntity entity) + { + StaticRouteProcessor handler = _processorFunctions[GetArrayOffsetForMethod(entity.Server.Method)]; + + if (!handler.Protection.CheckProtection(entity)) + { + //Allow the protection handler to define a custom response code + entity.CloseResponse(handler.Protection.ErrorCode); + return new(VfReturnType.VirtualSkip); + } + + return handler.WorkFunction(entity); + } + + /* + * This function will get an array offset that corresponds + * to the bit position of the calling method. This is used to + * get the processing function for the desired http method. + */ + private static int GetArrayOffsetForMethod(HttpMethod method) + { + return BitOperations.TrailingZeroCount((long)method); + } + + private static void InitProcessors(StaticRouteHandler[] routes, StaticRouteProcessor[] processors) + { + //Assign the default handler to all positions during initialization + Array.Fill(processors, StaticRouteProcessor.DefaultProcessor); + + //Then assign each route to the correct position based on the method + foreach (StaticRouteHandler route in routes) + { + int offset = GetArrayOffsetForMethod(route.Route.Method); + + processors[offset] = StaticRouteProcessor.FromRoute(route); + } + } + + private sealed class StaticRouteProcessor( + EndpointWorkFunc workFunc, + HttpProtectionHandler protection + ) + { + public readonly EndpointWorkFunc WorkFunction = workFunc; + public readonly HttpProtectionHandler Protection = protection; + + /// + /// Gets the default (not found) processor for static routes + /// + internal static readonly StaticRouteProcessor DefaultProcessor = new( + DefaultHandler, + HttpProtectionHandler.Create(null) + ); + + internal static StaticRouteProcessor FromRoute(StaticRouteHandler handler) + => new(handler.WorkFunc, handler.Protection); + + /* + * This function acts as the default handler in case a route or + * http method is not defined + */ + private static ValueTask DefaultHandler(HttpEntity _) + => new(VfReturnType.NotFound); + } + } + + + private delegate ValueTask EndpointWorkFunc(HttpEntity entity); + + private sealed class HttpProtectionHandler + { + private static readonly HttpProtectionHandler _default = new(); + + public readonly HttpStatusCode ErrorCode; + + private readonly bool _enabled; + private readonly bool _allowNewSessions; + private readonly SessionType _sesType; + private readonly AuthorzationCheckLevel _authLevel; + + public HttpProtectionHandler(HttpRouteProtectionAttribute protectionSettings) + { + _enabled = true; + _allowNewSessions = protectionSettings.AllowNewSession; + _sesType = protectionSettings.SessionType; + _authLevel = protectionSettings.AuthLevel; + ErrorCode = protectionSettings.ErrorCode; + } + + private HttpProtectionHandler() + { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CheckProtection(HttpEntity entity) + { + //If protection is disabled, always return true + if (!_enabled) + { + return true; + } + + return entity.Session.IsSet + && _allowNewSessions || !entity.Session.IsNew //May require reused sessions + && entity.Session.SessionType == _sesType + && entity.IsClientAuthorized(_authLevel); + } + + public static HttpProtectionHandler Create(HttpRouteProtectionAttribute? attr) + { + return attr is null + ? _default + : new(attr); + } + } + + private sealed class StaticRouteHandler + { + public required IHttpController Parent; + public required string Path; + public required HttpStaticRouteAttribute Route; + public required EndpointWorkFunc WorkFunc; + public required HttpProtectionHandler Protection; + } + + + private record RoutesWithSamePathGroup(string Path, StaticRouteHandler[] Routes); + } +} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/RoutingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/RoutingExtensions.cs index 6665a75..39a2d83 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/RoutingExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/RoutingExtensions.cs @@ -24,19 +24,14 @@ using System; using System.Reflection; -using System.Threading.Tasks; -using System.Collections.Frozen; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Runtime.CompilerServices; -using VNLib.Net.Http; using VNLib.Utils.Logging; using VNLib.Utils.Resources; using VNLib.Plugins.Essentials.Runtime; -using VNLib.Plugins.Essentials; -using VNLib.Plugins.Essentials.Endpoints; -using VNLib.Plugins.Extensions.Loading.Routing.Mvc; + namespace VNLib.Plugins.Extensions.Loading.Routing { @@ -110,6 +105,7 @@ namespace VNLib.Plugins.Extensions.Loading.Routing return pBase ?? throw new InvalidOperationException("Endpoint was not dynamically routed"); } + private static readonly Regex ConfigSyntaxParser = ParserRegex(); private delegate void InitFunc(string path, ILogProvider log); @@ -119,9 +115,7 @@ namespace VNLib.Plugins.Extensions.Loading.Routing private static void InitEndpointSettings(PluginBase plugin, T endpoint) where T : IEndpoint { //Load optional config - IConfigScope config = plugin.GetConfigForType(); - - ILogProvider logger = plugin.Log; + IConfigScope? config = plugin.TryGetConfigForType(); EndpointPathAttribute? pathAttr = typeof(T).GetCustomAttribute(); @@ -136,18 +130,13 @@ namespace VNLib.Plugins.Extensions.Loading.Routing return; } - string? logName = typeof(T).GetCustomAttribute()?.LogName; + ILogProvider logger = ConfigureLogger(plugin, config); - if (!string.IsNullOrWhiteSpace(logName)) - { - logger = plugin.Log.CreateScope(SubsituteValue(logName, config)); - } try { - //Invoke init function and pass in variable names initPathAndLog( - path: SubsituteValue(pathAttr.Path, config), + path: SubsituteConfigStringValue(pathAttr.Path, config), logger ); } @@ -159,68 +148,45 @@ namespace VNLib.Plugins.Extensions.Loading.Routing { throw new ConfigurationException($"Failed to initalize endpoint {endpoint.GetType().Name}", e); } - - static string SubsituteValue(string pathVar, IConfigScope? config) - { - if (config is null) - { - return pathVar; - } - - // Replace the matched pattern with the corresponding value from the dictionary - return ConfigSyntaxParser.Replace(pathVar, match => - { - string varName = match.Groups[1].Value; - - //Get the value from the config scope or return the original variable unmodified - return config.GetValueOrDefault(varName, varName); - }); - } - } - - private sealed class EndpointCollection : IVirtualEndpointDefinition - { - public List Endpoints { get; } = new(); - - /// - IEnumerable IVirtualEndpointDefinition.GetEndpoints() => Endpoints; } - - private delegate ValueTask EndpointWorkFunc(HttpEntity entity); - - sealed record class HttpControllerEndpoint(MethodInfo MethodInfo, HttpEndpointAttribute Attr) + internal static string SubsituteConfigStringValue(string pathVar, IConfigScope? config) { - public string Path => Attr.Path; + if (config is null) + { + return pathVar; + } - public HttpMethod Method => Attr.Method; + // Replace the matched pattern with the corresponding value from the dictionary + return ConfigSyntaxParser.Replace(pathVar, match => + { + string varName = match.Groups[1].Value; - public EndpointWorkFunc Func { get; } = MethodInfo.CreateDelegate(); + //Get the value from the config scope or return the original variable unmodified + return config.GetValueOrDefault(varName, varName); + }); } - private sealed class EndpointWrapper - : ResourceEndpointBase + internal static ILogProvider ConfigureLogger(PluginBase plugin, IConfigScope? config) { + ILogProvider logger = plugin.Log; - private readonly FrozenDictionary _wrappers; + string? logName = typeof(T).GetCustomAttribute()?.LogName; - public EndpointWrapper(FrozenDictionary table, string path, ILogProvider log) + if (!string.IsNullOrWhiteSpace(logName)) { - _wrappers = table; - InitPathAndLog(path, log); + logger = plugin.Log.CreateScope(SubsituteConfigStringValue(logName, config)); } - protected override ValueTask OnProcessAsync(HttpEntity entity) - { - ref readonly EndpointWorkFunc func = ref _wrappers.GetValueRefOrNullRef(entity.Server.Method); + return logger; + } - if (Unsafe.IsNullRef(in func)) - { - return ValueTask.FromResult(VfReturnType.ProcessAsFile); - } + private sealed class EndpointCollection : IVirtualEndpointDefinition + { + public List Endpoints { get; } = []; - return func(entity); - } + /// + IEnumerable IVirtualEndpointDefinition.GetEndpoints() => Endpoints; } } } diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs index 9be74ee..8bf9d68 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs @@ -107,21 +107,6 @@ namespace VNLib.Plugins.Extensions.Loading return res ?? throw new KeyNotFoundException($"Missing required secret {secretName}"); } - /// - /// Gets a secret at the given vault url (in the form of "vault://[mount-name]/[secret-path]?secret=[secret_name]") - /// - /// - /// The raw vault url to lookup - /// The string of the object at the specified vault path - /// - /// - /// - [Obsolete("Deprecated in favor of Secrets")] - public static Task GetSecretFromVaultAsync(this PluginBase plugin, ReadOnlySpan vaultPath) - { - throw new NotSupportedException("This method is not supported in this context"); - } - /// /// Gets the Secret value as a byte buffer /// -- cgit