aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs107
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs13
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpControllerAttribute.cs36
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpRouteProtectionAttribute.cs63
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpStaticRouteAttribute.cs (renamed from lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpEndpointAttribute.cs)13
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/IHttpController.cs52
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/MvcExtensions.cs329
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Routing/RoutingExtensions.cs92
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs15
9 files changed, 551 insertions, 169 deletions
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";
- /// <summary>
- /// Retrieves a top level configuration dictionary of elements for the specified type.
- /// The type must contain a <see cref="ConfigurationNameAttribute"/>
- /// </summary>
- /// <typeparam name="T">The type to get the configuration of</typeparam>
- /// <param name="plugin"></param>
- /// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
- /// <exception cref="ConfigurationException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- public static IConfigScope GetConfigForType<T>(this PluginBase plugin)
- {
- Type t = typeof(T);
- return plugin.GetConfigForType(t);
- }
-
- /// <summary>
- /// Retrieves a top level configuration dictionary of elements with the specified property name.
- /// </summary>
- /// <remarks>
- /// Search order: Plugin config, fall back to host config, throw if not found
- /// </remarks>
- /// <param name="plugin"></param>
- /// <param name="propName">The config property name to retrieve</param>
- /// <returns>A <see cref="IConfigScope"/> of top level configuration elements for the type</returns>
- /// <exception cref="ConfigurationException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- 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");
- }
/// <summary>
/// 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 <see cref="ConfigurationNameAttribute"/>
/// </summary>
/// <param name="plugin"></param>
- /// <param name="type">The type to get configuration data for</param>
- /// <returns>A <see cref="IConfigScope"/> of top level configuration elements for the type</returns>
+ /// <param name="type">The class type to get the configuration scope variable from</param>
+ /// <returns>A <see cref="IConfigScope"/> for the desired top-level configuration scope</returns>
+ /// <exception cref="ConfigurationException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
- 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);
+ /// <summary>
+ /// Retrieves a top level configuration dictionary of elements for the specified type.
+ /// The type must contain a <see cref="ConfigurationNameAttribute"/>
+ /// </summary>
+ /// <typeparam name="T">The type to get the configuration of</typeparam>
+ /// <param name="plugin"></param>
+ /// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
+ /// <exception cref="ConfigurationException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static IConfigScope? TryGetConfigForType<T>(this PluginBase plugin)
+ {
+ return TryGetConfigForType(plugin, typeof(T));
+ }
+
+ /// <summary>
+ /// Retrieves a top level configuration dictionary of elements for the specified type.
+ /// The type must contain a <see cref="ConfigurationNameAttribute"/>
+ /// </summary>
+ /// <param name="plugin"></param>
+ /// <param name="type">The type to get configuration data for</param>
+ /// <returns>A <see cref="IConfigScope"/> of top level configuration elements for the type</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static IConfigScope GetConfigForType(this PluginBase plugin, Type type)
+ {
+ return TryGetConfigForType(plugin, type)
+ ?? throw new ConfigurationException($"Missing required configuration key for type {type.Name}");
+ }
+
+ /// <summary>
+ /// Retrieves a top level configuration dictionary of elements for the specified type.
+ /// The type must contain a <see cref="ConfigurationNameAttribute"/>
+ /// </summary>
+ /// <typeparam name="T">The type to get the configuration of</typeparam>
+ /// <param name="plugin"></param>
+ /// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
+ /// <exception cref="ConfigurationException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static IConfigScope GetConfigForType<T>(this PluginBase plugin)
+ {
+ return GetConfigForType(plugin, typeof(T));
+ }
+
+ /// <summary>
+ /// Retrieves a top level configuration dictionary of elements with the specified property name.
+ /// </summary>
+ /// <remarks>
+ /// Search order: Plugin config, fall back to host config, throw if not found
+ /// </remarks>
+ /// <param name="plugin"></param>
+ /// <param name="propName">The config property name to retrieve</param>
+ /// <returns>A <see cref="IConfigScope"/> of top level configuration elements for the type</returns>
+ /// <exception cref="ConfigurationException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ 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");
}
+
/// <summary>
/// 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
/// <exception cref="ConcreteTypeAmbiguousMatchException"></exception>
public static T CreateService<T>(this PluginBase plugin)
{
- if (plugin.HasConfigForType<T>())
- {
- IConfigScope config = plugin.GetConfigForType<T>();
- return CreateService<T>(plugin, config);
- }
- else
- {
- return CreateService<T>(plugin, (IConfigScope?)null);
- }
+ return CreateService<T>(
+ plugin,
+ config: plugin.TryGetConfigForType<T>()
+ );
}
/// <summary>
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
-{
- /// <summary>
- /// Attribute to define a controller for http routing. The class must be decorated
- /// with this attribute to be recognized as a controller
- /// </summary>
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
- public sealed class HttpControllerAttribute : Attribute
- { }
-}
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
+{
+ /// <summary>
+ /// When applied to a method, this attribute will require the client to have a valid
+ /// authorization in order to access the endpoint.
+ /// </summary>
+ /// <param name="authLevel">The protection authorization level</param>
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public sealed class HttpRouteProtectionAttribute(AuthorzationCheckLevel authLevel) : Attribute
+ {
+ /// <summary>
+ /// Defines the allowed session types for this endpoint
+ /// </summary>
+ public SessionType SessionType { get; init; } = SessionType.Web;
+
+ /// <summary>
+ /// The minimum authorization level required to access the endpoint
+ /// </summary>
+ public AuthorzationCheckLevel AuthLevel { get; } = authLevel;
+
+ /// <summary>
+ /// The status code to return when the client is not authorized
+ /// </summary>
+ public HttpStatusCode ErrorCode { get; init; } = HttpStatusCode.Unauthorized;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public bool AllowNewSession { get; init; }
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpEndpointAttribute.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpStaticRouteAttribute.cs
index d89df63..e37c50b 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpEndpointAttribute.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpStaticRouteAttribute.cs
@@ -3,9 +3,9 @@
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
-* File: HttpEndpointAttribute.cs
+* File: HttpStaticRouteAttribute.cs
*
-* HttpEndpointAttribute.cs is part of VNLib.Plugins.Extensions.Loading which is
+* 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
@@ -30,13 +30,14 @@ namespace VNLib.Plugins.Extensions.Loading.Routing.Mvc
{
/// <summary>
- /// Attribute to define an http endpoint for a controller. The class
- /// must be decorated with the <see cref="HttpControllerAttribute"/> attribute
+ /// 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.
/// </summary>
- /// <param name="path">The endpoint path</param>
+ /// <param name="path">The static route path, may include configuration substitution variables</param>
/// <param name="method">The method (or methods) allowed to be filtered by this endpoint</param>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
- public sealed class HttpEndpointAttribute(string path, HttpMethod method) : Attribute
+ public sealed class HttpStaticRouteAttribute(string path, HttpMethod method) : Attribute
{
/// <summary>
/// The path of the endpoint
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
+{
+ /// <summary>
+ /// The base interface type for all http controllers, which
+ /// are responsible for handling http requests.
+ /// </summary>
+ public interface IHttpController
+ {
+ /// <summary>
+ /// Gets the protection settings for all routes within
+ /// this controller.
+ /// </summary>
+ /// <returns>The endpoint protection settings for all routes</returns>
+ ProtectionSettings GetProtectionSettings();
+
+ /// <summary>
+ /// Allows pre-processing of the http entity before
+ /// the request is processed by routing handlers
+ /// </summary>
+ /// <param name="entity">The request entity to pre-process</param>
+ /// <returns>A value that indicates if the request should continue processing or return</returns>
+ 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
+{
+ /// <summary>
+ /// Provides extension and helper classes for routing using MVC architecture
+ /// </summary>
+ public static class MvcExtensions
+ {
+ /// <summary>
+ /// Routes all endpoints for the specified controller
+ /// </summary>
+ /// <param name="plugin"></param>
+ /// <param name="controller">The controller instance to route endpoints for</param>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ public static T Route<T>(this PluginBase plugin, T? controller) where T : IHttpController
+ {
+ //If a null controller is passed (normal case) then create a new instance
+ controller ??= plugin.CreateService<T>();
+
+ IEndpoint[] staticEndpoints = GetStaticEndpointsForController(plugin, controller);
+
+ Array.ForEach(staticEndpoints, plugin.Route);
+
+ return controller;
+ }
+
+ /// <summary>
+ /// Routes all endpoints for the specified controller
+ /// </summary>
+ /// <param name="plugin"></param>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ public static T Route<T>(this PluginBase plugin) where T : IHttpController
+ => plugin.Route(default(T));
+
+ private static IEndpoint[] GetStaticEndpointsForController<T>(PluginBase plugin, T controller)
+ where T : IHttpController
+ {
+ IConfigScope? config = plugin.TryGetConfigForType<T>();
+ ILogProvider logger = RoutingExtensions.ConfigureLogger<T>(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>(T controller, IConfigScope? config)
+ where T : IHttpController
+ {
+ List<StaticRouteHandler> routes = [];
+
+ foreach (MethodInfo method in typeof(T).GetMethods())
+ {
+ HttpStaticRouteAttribute? route = method.GetCustomAttribute<HttpStaticRouteAttribute>();
+ HttpRouteProtectionAttribute? protection = method.GetCustomAttribute<HttpRouteProtectionAttribute>();
+
+ 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<EndpointWorkFunc>(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<RoutesWithSamePathGroup> 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();
+
+ /// <summary>
+ /// <inheritdoc/>
+ /// </summary>
+ 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);
+ }
+
+ ///<inheritdoc/>
+ protected override ERRNO PreProccess(HttpEntity entity)
+ {
+ return base.PreProccess(entity) && parent.PreProccess(entity);
+ }
+
+ ///<inheritdoc/>
+ protected override ValueTask<VfReturnType> 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;
+
+ /// <summary>
+ /// Gets the default (not found) processor for static routes
+ /// </summary>
+ 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<VfReturnType> DefaultHandler(HttpEntity _)
+ => new(VfReturnType.NotFound);
+ }
+ }
+
+
+ private delegate ValueTask<VfReturnType> 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<T>(PluginBase plugin, T endpoint) where T : IEndpoint
{
//Load optional config
- IConfigScope config = plugin.GetConfigForType<T>();
-
- ILogProvider logger = plugin.Log;
+ IConfigScope? config = plugin.TryGetConfigForType<T>();
EndpointPathAttribute? pathAttr = typeof(T).GetCustomAttribute<EndpointPathAttribute>();
@@ -136,18 +130,13 @@ namespace VNLib.Plugins.Extensions.Loading.Routing
return;
}
- string? logName = typeof(T).GetCustomAttribute<EndpointLogNameAttribute>()?.LogName;
+ ILogProvider logger = ConfigureLogger<T>(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<IEndpoint> Endpoints { get; } = new();
-
- ///<inheritdoc/>
- IEnumerable<IEndpoint> IVirtualEndpointDefinition.GetEndpoints() => Endpoints;
}
-
- private delegate ValueTask<VfReturnType> 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<EndpointWorkFunc>();
+ //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<T>(PluginBase plugin, IConfigScope? config)
{
+ ILogProvider logger = plugin.Log;
- private readonly FrozenDictionary<HttpMethod, EndpointWorkFunc> _wrappers;
+ string? logName = typeof(T).GetCustomAttribute<EndpointLogNameAttribute>()?.LogName;
- public EndpointWrapper(FrozenDictionary<HttpMethod, EndpointWorkFunc> table, string path, ILogProvider log)
+ if (!string.IsNullOrWhiteSpace(logName))
{
- _wrappers = table;
- InitPathAndLog(path, log);
+ logger = plugin.Log.CreateScope(SubsituteConfigStringValue(logName, config));
}
- protected override ValueTask<VfReturnType> 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<IEndpoint> Endpoints { get; } = [];
- return func(entity);
- }
+ ///<inheritdoc/>
+ IEnumerable<IEndpoint> 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
@@ -108,21 +108,6 @@ namespace VNLib.Plugins.Extensions.Loading
}
/// <summary>
- /// Gets a secret at the given vault url (in the form of "vault://[mount-name]/[secret-path]?secret=[secret_name]")
- /// </summary>
- /// <param name="plugin"></param>
- /// <param name="vaultPath">The raw vault url to lookup</param>
- /// <returns>The string of the object at the specified vault path</returns>
- /// <exception cref="UriFormatException"></exception>
- /// <exception cref="KeyNotFoundException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- [Obsolete("Deprecated in favor of Secrets")]
- public static Task<ISecretResult?> GetSecretFromVaultAsync(this PluginBase plugin, ReadOnlySpan<char> vaultPath)
- {
- throw new NotSupportedException("This method is not supported in this context");
- }
-
- /// <summary>
/// Gets the Secret value as a byte buffer
/// </summary>
/// <param name="secret"></param>