From 7be5d6648e633ba46a270ca5784de6f4a5a4e0a9 Mon Sep 17 00:00:00 2001 From: vnugent Date: Sun, 28 Jul 2024 19:15:04 -0400 Subject: Squashed commit of the following: commit a4dacd2909426bf628c1eee1253cc5c8a01e2691 Author: vnugent Date: Sat Jul 27 22:41:04 2024 -0400 package updates commit f836e09981866f5c9f2ae46990d11b186a7d12bb Author: vnugent Date: Wed Jul 24 19:15:54 2024 -0400 chore: Remove argon2 docs & optional tcp resuse commit b9b892ab2143b0ab92e4dcf0a8b043c5c6c17271 Author: vnugent Date: Sun Jul 21 20:57:01 2024 -0400 fix spelling Enqueue and deprecate mispelled version commit 21ffa816f18be4b765ad740ed5d93346ec3b1fda Author: vnugent Date: Sat Jul 20 19:44:31 2024 -0400 static arugment list parsing functions commit 85cd6793818a3edd0a963bb4829a960ee6b0e022 Author: vnugent Date: Mon Jul 15 18:58:06 2024 -0400 chore: Just some minor checks and adjustments commit abfb5761ee381b7e1e5342a5525ceca8c8fd81dd Author: vnugent Date: Thu Jul 4 23:57:37 2024 -0400 analyzer pass commit 4a96dbb924f2b5bf80293e4054f221efe67151dd Author: vnugent Date: Thu Jul 4 22:45:28 2024 -0400 package updates commit 38ad7d923fa8d9e463d4aaa8e35f021086a03f2d Author: vnugent Date: Thu Jul 4 16:20:48 2024 -0400 mimalloc merge upstream upgrades commit 981ba286e4793de95bf65e6588313411344c4d53 Author: vnugent Date: Thu Jul 4 16:04:03 2024 -0400 refactor: Refactor extensions with perf updates commit 6b8c67888731f7dd210acdb2b1160cdbdbe30d47 Author: vnugent Date: Fri Jun 28 15:48:22 2024 -0400 refactor: Update service stack to reflect new loading patterns commit 12391e9a207b60b41a074600fc2373ad3eb1c3ab Author: vnugent Date: Wed Jun 26 21:01:15 2024 -0400 feat(server): Server arch update, Memory struct access commit 92e182ceaf843f8d859d38faa8b2c0ff53207ff6 Author: vnugent Date: Fri Jun 21 16:02:34 2024 -0400 feat: Multi transport listeners commit ee3620b8168a42c8e571e853c751ad5999a9b907 Author: vnugent Date: Tue Jun 18 21:17:28 2024 -0400 feat: Add file path caching support commit ff0926be56fc6eafdce36411847d73bf4ce9f183 Author: vnugent Date: Sun Jun 16 13:08:31 2024 -0400 feat: Allow multiple plugin loading directories commit 07ddf6738d32127926d07b1366e56d2a2308b53b Author: vnugent Date: Sun Jun 16 01:12:07 2024 -0400 perf: Absolutely yuge perf boosts commit ff15c05a9c3e632c39f3889820fb7d889342b452 Author: vnugent Date: Fri Jun 14 14:16:24 2024 -0400 fix: Improper request buffer property assignment commit 7d2987f1d4048c30808a85798e32c99747f6cfe3 Author: vnugent Date: Thu Jun 13 21:57:34 2024 -0400 perf: Async pre-buffer to avoid sync buffer commit 75c1d0cbf9a5a7856c544671a45f1b4312ffe7ce Author: vnugent Date: Tue Jun 11 22:11:45 2024 -0400 feat: Add a default site adapater and interceptors commit a7c739b7db9a17622cee751fe0e8a10e4b84b48b Author: vnugent Date: Sun Jun 9 13:05:12 2024 -0400 chore: Package updated commit b4b506a4b6c7c1e90b5b0980e4cfe0460e7546a2 Author: vnugent Date: Sat Jun 8 21:54:52 2024 -0400 some minor touchups commit 2160510fcc22a8574b0090fd91ca29072f45ab59 Author: vnugent Date: Fri May 31 15:12:20 2024 -0400 refactor: Immutable tcp listeners commit 51cb4eb93e4f1b4c47d35b105e72af1fe771abcc Author: vnugent Date: Thu May 30 17:31:16 2024 -0400 refactor: minor non-breaking changes to VNEncoding commit 768ddc1eb949266d693f96c67d734e881bd59374 Merge: 9a835fe 1b590c2 Author: vnugent Date: Wed May 22 17:50:57 2024 -0400 Merge branch 'main' into develop commit 9a835fe12c9586ab8dd44d7c96fef4a2d6017e4b Author: vnugent Date: Fri May 17 18:27:03 2024 -0400 chore: Update mimmaloc v2.1.6, update fPIC & cleanup commit 3b7004b88acfc7f7baa3a8857a5a2f7cf3dd560e Author: vnugent Date: Fri May 17 16:03:28 2024 -0400 feat: Added ReadFileDataAsync function commit 9a964795757bf0da4dd7fcab15ad304f4ea3fdf1 Author: vnugent Date: Wed May 15 21:57:39 2024 -0400 refactor: Harden some argon2 password hashing commit 4035c838c1508af0aa7e767a97431a692958ce1c Author: vnugent Date: Sun May 12 16:55:32 2024 -0400 perf: Utils + http perf mods commit f4f0d4f74250257991c57bfae74c4852c7e1ae46 Author: vnugent Date: Thu May 2 15:22:53 2024 -0400 feat: Buff middleware handlers | | Added implicit support for middleware post processing of files before the filehandler closes the connection. Also cleaned up some project file stuff commit f0b7dca107659df1d7d4631fdbd2aae9d716d053 Merge: 8c4a45e 107b058 Author: vnugent Date: Sat Apr 20 12:24:05 2024 -0400 Merge branch 'main' into develop commit 8c4a45e384accf92b1b6d748530e8d46f7de40d6 Author: vnugent Date: Sat Apr 20 11:10:30 2024 -0400 refactor: Overhaul C libraries and fix builds commit 42ff77080d10b0fc9fecbbc46141e8e23a1d066a Author: vnugent Date: Sat Apr 20 00:45:57 2024 -0400 fix!: Middlware array, multiple cookie set, and cookie check commit 97e82b9d66f387f9e6d21d88ddc7a8ab8693149c Merge: 4ca5791 e07537a Author: vnugent Date: Tue Apr 2 13:34:22 2024 -0400 Merge branch 'main' into develop commit 4ca5791ed67b9834bdbd010206b30373e4705e9b Author: vnugent Date: Tue Apr 2 13:32:12 2024 -0400 fix: Missed ! on null pointer check commit 9b4036377c52200c6488c98180d69a0e63321f97 Author: vnugent Date: Tue Apr 2 13:22:29 2024 -0400 fix: Fix _In_ macro for compression public api commit 53a7b4b5c5b67b4a4e06e1d9098cac4bcd6afd7c Merge: 448a93b 21130c8 Author: vnugent Date: Sun Mar 31 17:01:15 2024 -0400 Merge branch 'main' into develop commit 448a93bb1d18d032087afe2476ffccb98634a54c Author: vnugent Date: Sun Mar 31 16:56:51 2024 -0400 ci: fix third-party dir cleanup commit 9afed1427472da1ea13079f98dbe27339e55ee7d Author: vnugent Date: Sun Mar 31 16:43:15 2024 -0400 perf: Deprecate unsafememoryhandle span extensions commit 3ff90da4f02af47ea6d233fdd4445337ebe36452 Author: vnugent Date: Sat Mar 30 21:36:18 2024 -0400 refactor: Updates, advanced tracing, http optimizations commit 8d6b79b5ae309b36f265ba81529bcef8bfcd7414 Merge: 6c1667b 5585915 Author: vnugent Date: Sun Mar 24 21:01:31 2024 -0400 Merge branch 'main' into develop commit 6c1667be23597513537f8190e2f55d65eb9b7c7a Author: vnugent Date: Fri Mar 22 12:01:53 2024 -0400 refactor: Overhauled native library loading and lazy init commit ebf688f2f974295beabf7b5def7e6f6f150551d0 Author: vnugent Date: Wed Mar 20 22:16:17 2024 -0400 refactor: Update compression header files and macros + Ci build commit 9c7b564911080ccd5cbbb9851a0757b05e1e9047 Author: vnugent Date: Tue Mar 19 21:54:49 2024 -0400 refactor: JWK overhaul & add length getter to FileUpload commit 6d8c3444e09561e5957491b3cc1ae858e0abdd14 Author: vnugent Date: Mon Mar 18 16:13:20 2024 -0400 feat: Add FNV1a software checksum and basic correction tests commit 00d182088cecefc08ca80b1faee9bed3f215f40b Author: vnugent Date: Fri Mar 15 01:05:27 2024 -0400 chore: #6 Use utils filewatcher instead of built-in commit d513c10d9895c6693519ef1d459c6a5a76929436 Author: vnugent Date: Sun Mar 10 21:58:14 2024 -0400 source tree project location updated --- .../src/Construction/HttpServiceStackBuilder.cs | 68 +++-- .../src/Construction/HttpTransportMapping.cs | 37 +++ .../src/Construction/IDomainBuilder.cs | 24 +- .../src/Construction/IServiceGroupBuilder.cs | 60 ++++ .../src/Construction/ServiceBuilder.cs | 81 +++++ .../src/Construction/SsBuilderExtensions.cs | 322 +++++++++----------- .../src/Construction/VirtualHostConfiguration.cs | 33 +-- .../VirtualHostConstructionExtensions.cs | 107 +++++++ .../src/HttpServiceStack.cs | 10 +- .../src/IHostTransportInfo.cs | 47 --- .../src/IHttpPluginManager.cs | 63 ---- .../src/IManagedPlugin.cs | 63 ---- .../src/IManualPlugin.cs | 71 ----- .../src/IPluginInitializer.cs | 40 --- .../src/IServiceHost.cs | 7 +- .../src/PluginExtensions.cs | 57 ---- .../src/PluginManager.cs | 118 -------- .../src/PluginRutimeEventHandler.cs | 80 ----- .../src/PluginStackInitializer.cs | 327 --------------------- .../src/Plugins/IHttpPluginManager.cs | 63 ++++ .../src/Plugins/IManagedPlugin.cs | 63 ++++ .../src/Plugins/IManualPlugin.cs | 71 +++++ .../src/Plugins/IPluginInitializer.cs | 40 +++ .../src/Plugins/PluginExtensions.cs | 57 ++++ .../src/Plugins/PluginManager.cs | 118 ++++++++ .../src/Plugins/PluginRutimeEventHandler.cs | 80 +++++ .../src/Plugins/PluginStackInitializer.cs | 327 +++++++++++++++++++++ .../src/ServiceDomain.cs | 54 +--- .../src/ServiceGroup.cs | 9 +- 29 files changed, 1329 insertions(+), 1168 deletions(-) create mode 100644 lib/Plugins.Essentials.ServiceStack/src/Construction/HttpTransportMapping.cs create mode 100644 lib/Plugins.Essentials.ServiceStack/src/Construction/IServiceGroupBuilder.cs create mode 100644 lib/Plugins.Essentials.ServiceStack/src/Construction/ServiceBuilder.cs create mode 100644 lib/Plugins.Essentials.ServiceStack/src/Construction/VirtualHostConstructionExtensions.cs delete mode 100644 lib/Plugins.Essentials.ServiceStack/src/IHostTransportInfo.cs delete mode 100644 lib/Plugins.Essentials.ServiceStack/src/IHttpPluginManager.cs delete mode 100644 lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs delete mode 100644 lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs delete mode 100644 lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs delete mode 100644 lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs delete mode 100644 lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs delete mode 100644 lib/Plugins.Essentials.ServiceStack/src/PluginRutimeEventHandler.cs delete mode 100644 lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs create mode 100644 lib/Plugins.Essentials.ServiceStack/src/Plugins/IHttpPluginManager.cs create mode 100644 lib/Plugins.Essentials.ServiceStack/src/Plugins/IManagedPlugin.cs create mode 100644 lib/Plugins.Essentials.ServiceStack/src/Plugins/IManualPlugin.cs create mode 100644 lib/Plugins.Essentials.ServiceStack/src/Plugins/IPluginInitializer.cs create mode 100644 lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginExtensions.cs create mode 100644 lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginManager.cs create mode 100644 lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginRutimeEventHandler.cs create mode 100644 lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginStackInitializer.cs (limited to 'lib/Plugins.Essentials.ServiceStack') diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs index 3ae2183..34c68bf 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs @@ -28,16 +28,18 @@ using System.Collections.Generic; using VNLib.Net.Http; using VNLib.Plugins.Runtime; - +using VNLib.Plugins.Essentials.ServiceStack.Plugins; namespace VNLib.Plugins.Essentials.ServiceStack.Construction { + /// /// A data structure used to build/create a /// around a /// public sealed class HttpServiceStackBuilder { + private readonly ServiceBuilder _serviceBuilder = new(); /// /// Initializes a new that will @@ -47,8 +49,10 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction public HttpServiceStackBuilder() { } - private Action>? _hostBuilder; - private Func? _getServers; + internal ServiceBuilder ServiceBuilder => _serviceBuilder; + + private Action? _hostBuilder; + private Func, IHttpServer[]>? _getServers; private Func? _getPlugins; private IManualPlugin[]? manualPlugins; private bool loadConcurrently; @@ -59,7 +63,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction /// /// The callback method to build virtual hosts /// The current instance for chaining - public HttpServiceStackBuilder WithDomain(Action> hostBuilder) + public HttpServiceStackBuilder WithDomain(Action hostBuilder) { _hostBuilder = hostBuilder; return this; @@ -70,7 +74,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction /// /// A callback method that gets the http server implementation for the service group /// The current instance for chaining - public HttpServiceStackBuilder WithHttp(Func getServers) + public HttpServiceStackBuilder WithHttp(Func, IHttpServer[]> getServers) { _getServers = getServers; return this; @@ -90,24 +94,34 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction /// /// Configures the stack to use the built-in http server implementation /// - /// The transport builder callback function + /// The transport builder callback function /// The http configuration structure used to initalize servers /// The current instance for chaining - public HttpServiceStackBuilder WithBuiltInHttp(Func transport, HttpConfig config) - { - return WithBuiltInHttp(transport, sg => config); - } + public HttpServiceStackBuilder WithBuiltInHttp(Func, HttpTransportMapping[]> getTransports, HttpConfig config) + => WithBuiltInHttp(getTransports, _ => config); /// /// Configures the stack to use the built-in http server implementation /// - /// The transport builder callback function + /// A callback function that gets transport bindings for servie groups /// The http configuration builder callback method /// The current instance for chaining - public HttpServiceStackBuilder WithBuiltInHttp(Func transport, Func configCallback) - { - return WithHttp(sg => new HttpServer(configCallback(sg), transport(sg), sg.Hosts.Select(static p => p.Processor))); - } + public HttpServiceStackBuilder WithBuiltInHttp( + Func, HttpTransportMapping[]> getBindings, + Func, HttpConfig> configCallback + ) => WithHttp((sgs) => { + + HttpTransportBinding[] vhBindings = getBindings(sgs) + .Select(static s => + { + IEnumerable procs = s.Hosts.Select(static s => s.Processor); + return new HttpTransportBinding(s.Transport, procs); + }) + .ToArray(); + + // A single built-in http server can service an entire domain + return [ new HttpServer(configCallback(sgs), vhBindings) ]; + }); /// /// Adds a collection of manual plugin instances to the stack. Every call @@ -139,27 +153,27 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction /// public HttpServiceStack Build() { - _ = _hostBuilder ?? throw new ArgumentNullException("WithDomainBuilder", "You have not configured a service domain configuration callback"); _ = _getServers ?? throw new ArgumentNullException("WithHttp", "You have not configured a IHttpServer configuration callback"); + //Host builder callback is optional + _hostBuilder?.Invoke(ServiceBuilder); + //Inint the service domain ServiceDomain sd = new(); - if (!sd.BuildDomain(_hostBuilder)) - { - throw new ArgumentException("Failed to configure the service domain, you must expose at least one service host"); - } + sd.BuildDomain(_serviceBuilder); - LinkedList servers = new(); + //Get http servers from the user callback for the service domain, let the caller decide how to route them + IHttpServer[] servers = _getServers.Invoke(sd.ServiceGroups); - //enumerate hosts groups - foreach (ServiceGroup hosts in sd.ServiceGroups) + if (servers.Length == 0) { - //Create new server - IHttpServer server = _getServers.Invoke(hosts); + throw new ArgumentException("No service hosts were configured. You must define at least one virtual host for the domain"); + } - //Add server to internal list - servers.AddLast(server); + if(servers.Any(servers => servers is null)) + { + throw new ArgumentException("One or more servers were not initialized correctly. Check the server configuration callback"); } return new(servers, sd, GetPluginStack(sd)); diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpTransportMapping.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpTransportMapping.cs new file mode 100644 index 0000000..c5fae41 --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpTransportMapping.cs @@ -0,0 +1,37 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: HttpTransportMapping.cs +* +* HttpTransportMapping.cs is part of VNLib.Plugins.Essentials.ServiceStack which is +* part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack 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 2 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.ServiceStack 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.Net.Http; + +namespace VNLib.Plugins.Essentials.ServiceStack.Construction +{ + /// + /// Represents a mapping of service hosts to a transport provider + /// + /// The collection of service hosts to map to transports + /// The transport that will provide networking to the host collection + public record class HttpTransportMapping(IEnumerable Hosts, ITransportProvider Transport); +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/IDomainBuilder.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/IDomainBuilder.cs index 19d2a96..9bccf54 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/Construction/IDomainBuilder.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/IDomainBuilder.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.ServiceStack @@ -22,9 +22,7 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ -using System.IO; - -using VNLib.Utils.Logging; +using System; namespace VNLib.Plugins.Essentials.ServiceStack.Construction { @@ -34,19 +32,17 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction public interface IDomainBuilder { /// - /// Adds a single virtual host to the domain that must be configured. + /// Allows for defining a new virtual host for the domain by manually configuring it. /// - /// The service root directory - /// The virtual host event hook handler - /// The log provider - /// The instance - IVirtualHostBuilder WithVirtualHost(DirectoryInfo rootDirectory, IVirtualHostHooks hooks, ILogProvider Logger); + /// A callback function that passes the new host builder + /// The current instance + IDomainBuilder WithServiceGroups(Action builder); /// - /// Adds a single pre-configured virtual host to the domain + /// Adds a collection of hosts to the domain /// - /// The pre-configured virtual host configuration - /// The current instance - IDomainBuilder WithVirtualHost(VirtualHostConfiguration config); + /// + /// + IDomainBuilder WithHosts(IServiceHost[] host); } } diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/IServiceGroupBuilder.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/IServiceGroupBuilder.cs new file mode 100644 index 0000000..1ffeb5c --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/IServiceGroupBuilder.cs @@ -0,0 +1,60 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: IServiceGroupBuilder.cs +* +* IServiceGroupBuilder.cs is part of VNLib.Plugins.Essentials.ServiceStack which +* is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack 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 2 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.ServiceStack 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 VNLib.Utils.Logging; + +namespace VNLib.Plugins.Essentials.ServiceStack.Construction +{ + /// + /// Allows for defining service groups for the service stack to manage + /// + public interface IServiceGroupBuilder + { + /// + /// Adds a single virtual host to the domain that must be configured. + /// + /// The service root directory + /// The virtual host event hook handler + /// The log provider + /// The instance + IVirtualHostBuilder WithVirtualHost(DirectoryInfo rootDirectory, IVirtualHostHooks hooks, ILogProvider Logger); + + /// + /// Allows for defining a new virtual host for the domain by manually configuring it. + /// + /// A callback function that passes the new host builder + /// The current instance + IServiceGroupBuilder WithVirtualHost(Action builder); + + /// + /// Adds a single pre-configured virtual host to the domain + /// + /// The pre-configured virtual host configuration + /// The current instance + IServiceGroupBuilder WithVirtualHost(VirtualHostConfiguration config, object? userState); + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/ServiceBuilder.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/ServiceBuilder.cs new file mode 100644 index 0000000..4817ba0 --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/ServiceBuilder.cs @@ -0,0 +1,81 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: ServiceBuilder.cs +* +* ServiceBuilder.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack 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 2 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.ServiceStack 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.Collections.Generic; + + +namespace VNLib.Plugins.Essentials.ServiceStack.Construction +{ + /// + /// A data structure used to build groupings of service hosts used + /// to configure http servers. + /// + public sealed class ServiceBuilder + { + private readonly List>> _callbacks = []; + + /// + /// Adds callback function that will add a collection of service hosts + /// and passes a state paramter to the callback + /// + /// + /// The optional state parameter + /// The host collection to add new service hosts to + /// The current instance for chaining + public ServiceBuilder AddHostCollection(T state, Action, T> host) + => AddHostCollection(col => host.Invoke(col, state)); + + /// + /// Adds a callback function that will add a collection of service hosts + /// + /// The callback function to return the collection of hosts + /// The current instance for chaining + public ServiceBuilder AddHostCollection(Action> host) + { + ArgumentNullException.ThrowIfNull(host); + + _callbacks.Add(host); + return this; + } + + /// + /// Builds the collection from the user + /// defined service host arrays + /// + /// The numeration that builds the service groups + internal IEnumerable BuildGroups() + { + return _callbacks.Select(static cb => + { + LinkedList hosts = new(); + + cb.Invoke(hosts); + + return new ServiceGroup(hosts); + }); + } + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs index 4195553..62b5070 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs @@ -29,12 +29,12 @@ using System.Linq; using System.Collections.Frozen; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Security.Cryptography.X509Certificates; using VNLib.Utils.Logging; using VNLib.Utils.Extensions; using VNLib.Net.Http; using VNLib.Plugins.Essentials.Middleware; +using VNLib.Plugins.Essentials.ServiceStack.Plugins; namespace VNLib.Plugins.Essentials.ServiceStack.Construction { @@ -45,14 +45,6 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction public static class SsBuilderExtensions { - /// - /// Creates a new instance to define your - /// virtual hosts using a built-in event processor type - /// - /// - /// The used to define your service domain - public static IDomainBuilder WithDomain(this HttpServiceStackBuilder stack) => WithDomain(stack, vhc => FromVirtualHostConfig(vhc.Clone())); - /// /// Creates a new instance to define your /// virtual hosts with the supplied callback method @@ -62,167 +54,144 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction /// The service stack builder instance public static HttpServiceStackBuilder WithDomain(this HttpServiceStackBuilder stack, Action domainBuilder) { - domainBuilder(stack.WithDomain()); + domainBuilder(WithDomain(stack)); return stack; } /// - /// Creates a new with your custom type - /// that will be wrapped for runtime processing. + /// Creates a new instance to define your + /// virtual hosts using a built-in event processor type /// - /// /// - /// The custom event processor type - /// - public static IDomainBuilder WithDomain(this HttpServiceStackBuilder stack, Func callback) - where T : EventProcessor, IRuntimeServiceInjection - { - List configs = new(); - DomainBuilder domains = new(configs, stack); - - //Add callback to capture this collection of configs when built - stack.AddHosts(() => configs.Select(c => new CustomServiceHost(c.Clone(), callback(c))).ToArray()); - - return domains; - } - - /// - /// Adds a single instance to the virtual host - /// - /// - /// The middleware instance to add - /// - public static IVirtualHostBuilder WithMiddleware(this IVirtualHostBuilder vhBuilder, IHttpMiddleware middleware) - { - vhBuilder.WithOption(c => c.CustomMiddleware.Add(middleware)); - return vhBuilder; - } - - /// - /// Adds multiple instances to the virtual host - /// - /// - /// The array of middleware instances to add to the collection - /// - public static IVirtualHostBuilder WithMiddleware(this IVirtualHostBuilder vhBuilder, params IHttpMiddleware[] middleware) - { - vhBuilder.WithOption(c => Array.ForEach(middleware, m => c.CustomMiddleware.Add(m))); - return vhBuilder; - } - - - public static IVirtualHostBuilder WithLogger(this IVirtualHostBuilder vhBuilder, ILogProvider logger) - { - vhBuilder.WithOption(c => c.LogProvider = logger); - return vhBuilder; - } - - public static IVirtualHostBuilder WithEndpoint(this IVirtualHostBuilder vhBuilder, IPEndPoint endpoint) - { - vhBuilder.WithOption(c => c.TransportEndpoint = endpoint); - return vhBuilder; - } - - public static IVirtualHostBuilder WithTlsCertificate(this IVirtualHostBuilder vhBuilder, X509Certificate? cert) - { - vhBuilder.WithOption(c => c.Certificate = cert); - return vhBuilder; - } + /// The used to define your service domain + public static IDomainBuilder WithDomain(this HttpServiceStackBuilder stack) + => new DomainBuilder(stack.ServiceBuilder); - public static IVirtualHostBuilder WithHostname(this IVirtualHostBuilder virtualHostBuilder, string hostname) - { - virtualHostBuilder.WithOption(c => c.Hostname = hostname); - return virtualHostBuilder; - } - public static IVirtualHostBuilder WithDefaultFiles(this IVirtualHostBuilder vhBuidler, params string[] defaultFiles) + private sealed class DomainBuilder(ServiceBuilder svcBuilder) : IDomainBuilder { - return vhBuidler.WithDefaultFiles((IReadOnlyCollection)defaultFiles); - } - - public static IVirtualHostBuilder WithDefaultFiles(this IVirtualHostBuilder vhBuidler, IReadOnlyCollection defaultFiles) - { - vhBuidler.WithOption(c => c.DefaultFiles = defaultFiles); - return vhBuidler; - } + /// + public IDomainBuilder WithServiceGroups(Action builder) + { + svcBuilder.AddHostCollection((col) => + { + SvGroupBuilder group = new(); - public static IVirtualHostBuilder WithExcludedExtensions(this IVirtualHostBuilder vhBuilder, params string[] excludedExtensions) - { - return vhBuilder.WithExcludedExtensions(new HashSet(excludedExtensions)); - } + builder(group); - public static IVirtualHostBuilder WithExcludedExtensions(this IVirtualHostBuilder vhBuilder, IReadOnlySet excludedExtensions) - { - vhBuilder.WithOption(c => c.ExcludedExtensions = excludedExtensions); - return vhBuilder; - } + group.Configs + .SelectMany(static vc => FromVirtualHostConfig(vc) + .Select(vh => new CustomServiceHost(vh, vc.UserState) + )) + .ForEach(col.Add); //Force enumeration + }); - public static IVirtualHostBuilder WithAllowedAttributes(this IVirtualHostBuilder vhBuilder, FileAttributes attributes) - { - vhBuilder.WithOption(c => c.AllowedAttributes = attributes); - return vhBuilder; - } - - public static IVirtualHostBuilder WithDisallowedAttributes(this IVirtualHostBuilder vhBuilder, FileAttributes attributes) - { - vhBuilder.WithOption(c => c.DissallowedAttributes = attributes); - return vhBuilder; - } + return this; + } - public static IVirtualHostBuilder WithDownstreamServers(this IVirtualHostBuilder vhBuilder, IReadOnlySet addresses) - { - vhBuilder.WithOption(c => c.DownStreamServers = addresses); - return vhBuilder; - } + /// + public IDomainBuilder WithHosts(IServiceHost[] hosts) + { + svcBuilder.AddHostCollection(col => Array.ForEach(hosts, col.Add)); + return this; + } - /// - /// Adds an array of IP addresses to the downstream server collection. This is a security - /// features that allows event handles to trust connections/ipaddresses that originate from - /// trusted downstream servers - /// - /// - /// The collection of IP addresses to set as trusted servers - /// - public static IVirtualHostBuilder WithDownstreamServers(this IVirtualHostBuilder vhBuilder, params IPAddress[] addresses) - { - vhBuilder.WithOption(c => c.DownStreamServers = new HashSet(addresses)); - return vhBuilder; - } + private static IEnumerable FromVirtualHostConfig(VirtualHostConfiguration configuration) + { + /* + * Configurations are allowed to define multiple hostnames for a single + * virtual host. + */ - private static BasicVirtualHost FromVirtualHostConfig(VirtualHostConfiguration configuration) - { - /* - * Event processors configurations are considered immutable. That is, - * top-level elements are not allowed to be changed after the processor - * has been created. Some properties/containers are allowed to be modified - * such as middleware chains, and the service pool. - */ + return configuration.Hostnames + .Select((string hostname) => + { + /* + * Event processors configurations are considered immutable. That is, + * top-level elements are not allowed to be changed after the processor + * has been created. Some properties/containers are allowed to be modified + * such as middleware chains, and the service pool. + */ + + EventProcessorConfig conf = new( + Directory: configuration.RootDir.FullName, + Hostname: hostname, + Log: configuration.LogProvider, + Options: configuration + ) + { + AllowedAttributes = configuration.AllowedAttributes, + DissallowedAttributes = configuration.DissallowedAttributes, + DefaultFiles = configuration.DefaultFiles, + ExecutionTimeout = configuration.ExecutionTimeout, + FilePathCacheMaxAge = configuration.FilePathCacheMaxAge, + + //Frozen sets are required for the event processor, for performance reasons + DownStreamServers = configuration.DownStreamServers.ToFrozenSet(), + ExcludedExtensions = configuration.ExcludedExtensions.ToFrozenSet(), + }; + + //Add all pre-configured middleware to the chain + configuration.CustomMiddleware.ForEach(conf.MiddlewareChain.Add); + + return new(configuration.EventHooks, conf); + }); + } - EventProcessorConfig conf = new( - configuration.RootDir.FullName, - configuration.Hostname, - configuration.LogProvider, - configuration) + private sealed record class SvGroupBuilder : IServiceGroupBuilder { - AllowedAttributes = configuration.AllowedAttributes, - DissallowedAttributes = configuration.DissallowedAttributes, - DefaultFiles = configuration.DefaultFiles, - ExecutionTimeout = configuration.ExecutionTimeout, + internal readonly List Configs = new(); - //Frozen sets are required for the event processor, for performance reasons - DownStreamServers = configuration.DownStreamServers.ToFrozenSet(), - ExcludedExtensions = configuration.ExcludedExtensions.ToFrozenSet(), - }; + /// + public IVirtualHostBuilder WithVirtualHost(DirectoryInfo rootDirectory, IVirtualHostHooks hooks, ILogProvider logger) + { + //Create new config instance and add to list + VirtualHostConfiguration config = new() + { + EventHooks = hooks, + RootDir = rootDirectory, + LogProvider = logger + }; + Configs.Add(config); + return new VHostBuilder(config); + } - //Add all pre-configured middleware to the chain - configuration.CustomMiddleware.ForEach(conf.MiddlewareChain.Add); + /// + public IServiceGroupBuilder WithVirtualHost(Action builder) + { + //Create new config instance and add to list + VirtualHostConfiguration config = new() + { + RootDir = null!, + LogProvider = null! + }; + + //Pass the builder to the callback + builder(new VHostBuilder(config)); + + return WithVirtualHost(config, null); + } - return new(configuration.EventHooks, conf); - } + /// + public IServiceGroupBuilder WithVirtualHost(VirtualHostConfiguration config, object? userState) + { + config.UserState = userState; + Configs.Add(config); + return this; + } + private sealed record class VHostBuilder(VirtualHostConfiguration Config) : IVirtualHostBuilder + { + /// + public IVirtualHostBuilder WithOption(Action configCallback) + { + configCallback(Config); + return this; + } + } + } + } - private static void AddHosts(this HttpServiceStackBuilder stack, Func hosts) - => stack.WithDomain(p => Array.ForEach(hosts(), h => p.Add(h))); private static void OnPluginServiceEvent(this IManagedPlugin plugin, Action loader) { @@ -232,48 +201,21 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction } } - private sealed record class DomainBuilder(List Configs, HttpServiceStackBuilder Stack) : IDomainBuilder - { - /// - public IVirtualHostBuilder WithVirtualHost(DirectoryInfo rootDirectory, IVirtualHostHooks hooks, ILogProvider logger) - { - //Create new config instance and add to list - VirtualHostConfiguration config = new() - { - EventHooks = hooks, - RootDir = rootDirectory, - LogProvider = logger - }; - Configs.Add(config); - return new VHostBuilder(config); - } - /// - public IDomainBuilder WithVirtualHost(VirtualHostConfiguration config) - { - Configs.Add(config); - return this; - } + /* + * The goal of this class is to added the extra service injection + * and manage the IWebRoot instance that will be served by a + * webserver + */ - private sealed record class VHostBuilder(VirtualHostConfiguration Config) : IVirtualHostBuilder - { - /// - public IVirtualHostBuilder WithOption(Action configCallback) - { - configCallback(Config); - return this; - } - } - } - - private sealed class CustomServiceHost(IHostTransportInfo Config, T Instance) : IServiceHost + private sealed class CustomServiceHost(T Instance, object? userState) : IServiceHost where T : EventProcessor, IRuntimeServiceInjection { /// public IWebRoot Processor => Instance; /// - public IHostTransportInfo TransportInfo => Config; + public object? UserState => userState; /// void IServiceHost.OnRuntimeServiceAttach(IManagedPlugin plugin, IEndpoint[] endpoints) @@ -300,10 +242,12 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction plugin.OnPluginServiceEvent>(p => p.ForEach(Instance.Options.MiddlewareChain.Remove)); plugin.OnPluginServiceEvent(p => p.ForEach(Instance.Options.MiddlewareChain.Remove)); } + } - private sealed class BasicVirtualHost(IVirtualHostHooks Hooks, EventProcessorConfig config) : EventProcessor(config), IRuntimeServiceInjection + private sealed class BasicVirtualHost(IVirtualHostHooks Hooks, EventProcessorConfig config) + : EventProcessor(config), IRuntimeServiceInjection { /* * Runtime service injection can be tricky, at least in my architecture. If all we have @@ -315,16 +259,20 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction private readonly ConditionalWeakTable _exposedTypes = new(); /// - public override bool ErrorHandler(HttpStatusCode errorCode, IHttpEvent entity) => Hooks.ErrorHandler(errorCode, entity); + public override bool ErrorHandler(HttpStatusCode errorCode, IHttpEvent entity) + => Hooks.ErrorHandler(errorCode, entity); /// - public override void PreProcessEntity(HttpEntity entity, out FileProcessArgs preProcArgs) => Hooks.PreProcessEntityAsync(entity, out preProcArgs); + public override void PreProcessEntity(HttpEntity entity, out FileProcessArgs preProcArgs) + => Hooks.PreProcessEntityAsync(entity, out preProcArgs); /// - public override void PostProcessEntity(HttpEntity entity, ref FileProcessArgs chosenRoutine) => Hooks.PostProcessFile(entity, ref chosenRoutine); + public override void PostProcessEntity(HttpEntity entity, ref FileProcessArgs chosenRoutine) + => Hooks.PostProcessFile(entity, ref chosenRoutine); /// - public override string TranslateResourcePath(string requestPath) => Hooks.TranslateResourcePath(requestPath); + public override string TranslateResourcePath(string requestPath) + => Hooks.TranslateResourcePath(requestPath); /// public void AddServices(IServiceProvider services) diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/VirtualHostConfiguration.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/VirtualHostConfiguration.cs index 97ad905..20e346e 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/Construction/VirtualHostConfiguration.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/VirtualHostConfiguration.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.ServiceStack @@ -26,7 +26,6 @@ using System; using System.IO; using System.Net; using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; using VNLib.Utils.Logging; using VNLib.Plugins.Essentials.Middleware; @@ -36,34 +35,28 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction /// /// A virtual host configuration container /// - public class VirtualHostConfiguration : IHostTransportInfo, IEpProcessingOptions + public class VirtualHostConfiguration : IEpProcessingOptions { /// - /// The directory that this virtual host will serve files from - /// - public DirectoryInfo RootDir { get; set; } = null!; - - /// - /// The hostname, or domain name, that this virtual host will respond to - /// Default: * + /// Optional user state object /// - public string Hostname { get; set; } = "*"; + internal object? UserState { get; set; } /// - /// The transport endpoint that this virtual host will listen on - /// Default: 0.0.0.0:80 + /// The directory that this virtual host will serve files from /// - public IPEndPoint TransportEndpoint { get; set; } = new IPEndPoint(IPAddress.Any, 80); + public required DirectoryInfo RootDir { get; set; } /// - /// An optional certificate to use for TLS connections + /// The hostname, or domain name, that this virtual host will respond to + /// Default: * /// - public X509Certificate? Certificate { get; set; } + public string[] Hostnames { get; set; } = [ "*" ]; /// /// A log provider to use for this virtual host /// - public ILogProvider LogProvider { get; set; } = null!; + public required ILogProvider LogProvider { get; set; } /// /// The name of a default file to search for within a directory if no file is specified (index.html). @@ -112,6 +105,12 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction /// public ICollection CustomMiddleware { get; } = new List(); + /// + /// A for how long a file path may be cached before being revalidated. Setting to + /// zero will disable path caching + /// + public TimeSpan FilePathCacheMaxAge { get; set; } = TimeSpan.Zero; + internal VirtualHostConfiguration Clone() => (VirtualHostConfiguration)MemberwiseClone(); } } diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/VirtualHostConstructionExtensions.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/VirtualHostConstructionExtensions.cs new file mode 100644 index 0000000..acf1b53 --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/VirtualHostConstructionExtensions.cs @@ -0,0 +1,107 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: SsBuilderExtensions.cs +* +* SsBuilderExtensions.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack 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 2 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.ServiceStack 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.Net; +using System.Collections.Generic; + +using VNLib.Utils.Logging; +using VNLib.Plugins.Essentials.Middleware; + +namespace VNLib.Plugins.Essentials.ServiceStack.Construction +{ + public static class VirtualHostConstructionExtensions + { + /// + /// Adds a single instance to the virtual host + /// + /// + /// The middleware instance to add + /// + public static IVirtualHostBuilder WithMiddleware(this IVirtualHostBuilder vhBuilder, IHttpMiddleware middleware) + => vhBuilder.WithOption(c => c.CustomMiddleware.Add(middleware)); + + /// + /// Adds multiple instances to the virtual host + /// + /// + /// The array of middleware instances to add to the collection + /// + public static IVirtualHostBuilder WithMiddleware(this IVirtualHostBuilder vhBuilder, params IHttpMiddleware[] middleware) + => vhBuilder.WithOption(c => Array.ForEach(middleware, m => c.CustomMiddleware.Add(m))); + + + /// + /// Takes a callback to allow you to inject middelware applications into + /// your virtual host + /// + /// + /// The array of middleware instances to add to the collection + /// + public static IVirtualHostBuilder WithMiddleware(this IVirtualHostBuilder vhBuilder, Action> middleware) + => vhBuilder.WithOption(c => middleware.Invoke(c.CustomMiddleware)); + + public static IVirtualHostBuilder WithLogger(this IVirtualHostBuilder vhBuilder, ILogProvider logger) + => vhBuilder.WithOption(c => c.LogProvider = logger); + + public static IVirtualHostBuilder WithHostnames(this IVirtualHostBuilder virtualHostBuilder, string[] hostnames) + => virtualHostBuilder.WithOption(c => c.Hostnames = hostnames); + + public static IVirtualHostBuilder WithDefaultFiles(this IVirtualHostBuilder vhBuidler, params string[] defaultFiles) + => vhBuidler.WithDefaultFiles((IReadOnlyCollection)defaultFiles); + + public static IVirtualHostBuilder WithDefaultFiles(this IVirtualHostBuilder vhBuidler, IReadOnlyCollection defaultFiles) + => vhBuidler.WithOption(c => c.DefaultFiles = defaultFiles); + + public static IVirtualHostBuilder WithExcludedExtensions(this IVirtualHostBuilder vhBuilder, params string[] excludedExtensions) + => vhBuilder.WithExcludedExtensions(new HashSet(excludedExtensions)); + + public static IVirtualHostBuilder WithExcludedExtensions(this IVirtualHostBuilder vhBuilder, IReadOnlySet excludedExtensions) + => vhBuilder.WithOption(c => c.ExcludedExtensions = excludedExtensions); + + public static IVirtualHostBuilder WithAllowedAttributes(this IVirtualHostBuilder vhBuilder, FileAttributes attributes) + => vhBuilder.WithOption(c => c.AllowedAttributes = attributes); + + public static IVirtualHostBuilder WithDisallowedAttributes(this IVirtualHostBuilder vhBuilder, FileAttributes attributes) + => vhBuilder.WithOption(c => c.DissallowedAttributes = attributes); + + public static IVirtualHostBuilder WithDownstreamServers(this IVirtualHostBuilder vhBuilder, IReadOnlySet addresses) + => vhBuilder.WithOption(c => c.DownStreamServers = addresses); + + public static IVirtualHostBuilder WithFilePathCache(this IVirtualHostBuilder vhBuilder, TimeSpan maxAge = default) + => vhBuilder.WithOption(c => c.FilePathCacheMaxAge = maxAge); + + /// + /// Adds an array of IP addresses to the downstream server collection. This is a security + /// features that allows event handles to trust connections/ipaddresses that originate from + /// trusted downstream servers + /// + /// + /// The collection of IP addresses to set as trusted servers + /// + public static IVirtualHostBuilder WithDownstreamServers(this IVirtualHostBuilder vhBuilder, params IPAddress[] addresses) + => vhBuilder.WithOption(c => c.DownStreamServers = new HashSet(addresses)); + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs index 71db38f..2f1bb0b 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs @@ -30,6 +30,7 @@ using System.Collections.Generic; using VNLib.Utils; using VNLib.Net.Http; using VNLib.Utils.Logging; +using VNLib.Plugins.Essentials.ServiceStack.Plugins; namespace VNLib.Plugins.Essentials.ServiceStack { @@ -39,7 +40,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// public sealed class HttpServiceStack : VnDisposeable { - private readonly LinkedList _servers; + private readonly IReadOnlyCollection _servers; private readonly ServiceDomain _serviceDomain; private readonly PluginManager _plugins; @@ -49,7 +50,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// /// A collection of all loaded servers /// - public IReadOnlyCollection Servers => _servers; + public IEnumerable Servers => _servers; /// /// Gets the internal that manages plugins for the entire @@ -62,7 +63,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// generate servers to listen for services exposed by the /// specified host context /// - internal HttpServiceStack(LinkedList servers, ServiceDomain serviceDomain, IPluginInitializer plugins) + internal HttpServiceStack(IReadOnlyCollection servers, ServiceDomain serviceDomain, IPluginInitializer plugins) { _servers = servers; _serviceDomain = serviceDomain; @@ -130,9 +131,6 @@ namespace VNLib.Plugins.Essentials.ServiceStack _cts?.Dispose(); _plugins.Dispose(); - - //remove all lists - _servers.Clear(); } } } diff --git a/lib/Plugins.Essentials.ServiceStack/src/IHostTransportInfo.cs b/lib/Plugins.Essentials.ServiceStack/src/IHostTransportInfo.cs deleted file mode 100644 index 5c663a9..0000000 --- a/lib/Plugins.Essentials.ServiceStack/src/IHostTransportInfo.cs +++ /dev/null @@ -1,47 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.ServiceStack -* File: IHostTransportInfo.cs -* -* IHostTransportInfo.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.ServiceStack 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 2 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.ServiceStack 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.Net; -using System.Security.Cryptography.X509Certificates; - -namespace VNLib.Plugins.Essentials.ServiceStack -{ - /// - /// Represents the service host's network/transport - /// information including the optional certificate and - /// the endpoint to listen on - /// - public interface IHostTransportInfo - { - /// - /// Optional TLS certificate to use - /// - X509Certificate? Certificate { get; } - - /// - /// The endpoint to listen on - /// - IPEndPoint TransportEndpoint { get; } - } -} diff --git a/lib/Plugins.Essentials.ServiceStack/src/IHttpPluginManager.cs b/lib/Plugins.Essentials.ServiceStack/src/IHttpPluginManager.cs deleted file mode 100644 index 6cece1f..0000000 --- a/lib/Plugins.Essentials.ServiceStack/src/IHttpPluginManager.cs +++ /dev/null @@ -1,63 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.ServiceStack -* File: IHttpPluginManager.cs -* -* IHttpPluginManager.cs is part of VNLib.Plugins.Essentials.ServiceStack which -* is part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.ServiceStack 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 2 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.ServiceStack 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.Collections.Generic; - -namespace VNLib.Plugins.Essentials.ServiceStack -{ - /// - /// Represents a live plugin controller that manages all - /// plugins loaded in a - /// - public interface IHttpPluginManager - { - /// - /// The the plugins managed by this - /// - public IEnumerable Plugins { get; } - - /// - /// Sends a message to a plugin identified by it's name. - /// - /// The name of the plugin to pass the message to - /// The message to pass to the plugin - /// The name string comparison type - /// True if the plugin was found and it has a message handler loaded - /// - bool SendCommandToPlugin(string pluginName, string message, StringComparison nameComparison = StringComparison.Ordinal); - - /// - /// Manually reloads all plugins loaded to the current service manager - /// - /// - /// - void ForceReloadAllPlugins(); - - /// - /// Unloads all loaded plugins and calls thier event handlers - /// - void UnloadPlugins(); - } -} diff --git a/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs deleted file mode 100644 index 8332f7e..0000000 --- a/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs +++ /dev/null @@ -1,63 +0,0 @@ -/* -* Copyright (c) 2024 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.ServiceStack -* File: IManagedPlugin.cs -* -* IManagedPlugin.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.ServiceStack 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 2 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.ServiceStack 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.Design; - -namespace VNLib.Plugins.Essentials.ServiceStack -{ - /// - /// Represents a plugin managed by a that includes dynamically loaded plugins - /// - public interface IManagedPlugin - { - /// - /// The exposed services the inernal plugin provides - /// - /// - /// WARNING: Services exposed by the plugin will abide by the plugin lifecycle, so consumers - /// must listen for plugin load/unload events to respect lifecycles properly. - /// - IServiceContainer Services { get; } - - /// - /// Internal notification that the plugin is loaded - /// - internal void OnPluginLoaded(); - - /// - /// Internal notification that the plugin is unloaded - /// - internal void OnPluginUnloaded(); - - /// - /// Sends the specified command to the desired plugin by it's name - /// - /// The name of the plugin to find - /// The command text to send to the plugin - /// The string name comparison type - /// True if the command was sent successfully - internal bool SendCommandToPlugin(string pluginName, string command, StringComparison comp); - } -} diff --git a/lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs deleted file mode 100644 index cecd481..0000000 --- a/lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs +++ /dev/null @@ -1,71 +0,0 @@ -/* -* Copyright (c) 2024 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.ServiceStack -* File: IManualPlugin.cs -* -* IManualPlugin.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.ServiceStack 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 2 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.ServiceStack 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.Design; - -namespace VNLib.Plugins.Essentials.ServiceStack -{ - /// - /// Represents a plugin that may be added to a service stack in user-code - /// instead of the conventional runtime plugin loading system - /// - public interface IManualPlugin : IDisposable - { - /// - /// The name of the plugin - /// - string Name { get; } - - /// - /// Collects all exported services for use within the service stack - /// - /// The container to add services to - void GetAllExportedServices(IServiceContainer container); - - /// - /// Initializes the plugin, called before accessing any other methods - /// - void Initialize(); - - /// - /// Loads the plugin, called after initialization but before getting - /// endpoints or services to allow for the plugin to configure itself - /// and perform initial setup - /// - void Load(); - - /// - /// Called when an unload was requested, either manually by the plugin controller - /// or when the service stack is unloading - /// - void Unload(); - - /// - /// Passes a console command to the plugin - /// - /// The raw command text to pass to the plugin from the console - void OnConsoleCommand(string command); - } -} diff --git a/lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs b/lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs deleted file mode 100644 index a9aa103..0000000 --- a/lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs +++ /dev/null @@ -1,40 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.ServiceStack -* File: IPluginInitializer.cs -* -* IPluginInitializer.cs is part of VNLib.Plugins.Essentials.ServiceStack which -* is part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.ServiceStack 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 2 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.ServiceStack 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.Utils.Logging; - -namespace VNLib.Plugins.Essentials.ServiceStack -{ - internal interface IPluginInitializer - { - IManagedPlugin[] InitializePluginStack(ILogProvider eventLogger); - - void UnloadPlugins(); - - void ReloadPlugins(); - - void Dispose(); - } -} diff --git a/lib/Plugins.Essentials.ServiceStack/src/IServiceHost.cs b/lib/Plugins.Essentials.ServiceStack/src/IServiceHost.cs index 2517d66..2b5dc64 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/IServiceHost.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/IServiceHost.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.ServiceStack @@ -23,6 +23,7 @@ */ using VNLib.Net.Http; +using VNLib.Plugins.Essentials.ServiceStack.Plugins; namespace VNLib.Plugins.Essentials.ServiceStack { @@ -40,9 +41,9 @@ namespace VNLib.Plugins.Essentials.ServiceStack IWebRoot Processor { get; } /// - /// The host's transport information + /// Optional user state to be set during initialization and read at a later time /// - IHostTransportInfo TransportInfo { get; } + object? UserState { get; } /// /// Called when a plugin is loaded and is endpoints are extracted diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs deleted file mode 100644 index 52377ee..0000000 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs +++ /dev/null @@ -1,57 +0,0 @@ -/* -* Copyright (c) 2024 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.ServiceStack -* File: PluginExtensions.cs -* -* PluginExtensions.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.ServiceStack 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 2 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.ServiceStack 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.Linq; -using System.Collections.Generic; - -using VNLib.Plugins.Essentials.Runtime; - -namespace VNLib.Plugins.Essentials.ServiceStack -{ - /// - /// Internal and service stack specific extensions for plugins - /// - public static class PluginExtensions - { - /// - /// Gets the endpoints exposed by the plugin - /// - /// - /// The enumeration of web endpoints - internal static IEnumerable GetEndpoints(this IManagedPlugin plugin) - { - //Try to get the endpoint defintion - if (plugin.Services.GetService(typeof(IVirtualEndpointDefinition)) is IVirtualEndpointDefinition defintion) - { - //Return the endpoints from the definition - return defintion.GetEndpoints(); - } - - //If the plugin does not have an endpoint definition, return an empty enumeration - return Enumerable.Empty(); - } - - internal static PluginRutimeEventHandler GetListener(this ServiceDomain domain) => new(domain); - } -} diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs deleted file mode 100644 index f43da78..0000000 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs +++ /dev/null @@ -1,118 +0,0 @@ -/* -* Copyright (c) 2024 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.ServiceStack -* File: PluginManager.cs -* -* PluginManager.cs is part of VNLib.Plugins.Essentials.ServiceStack which -* is part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.ServiceStack 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 2 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.ServiceStack 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.Collections.Generic; - -using VNLib.Utils; -using VNLib.Utils.Logging; - -namespace VNLib.Plugins.Essentials.ServiceStack -{ - - /// - /// A sealed type that manages the plugin interaction layer. Manages the lifetime of plugin - /// instances, exposes controls, and relays stateful plugin events. - /// - internal sealed class PluginManager(IPluginInitializer stack) : VnDisposeable, IHttpPluginManager - { - - /// - /// The collection of internal controllers - /// - public IEnumerable Plugins => _loadedPlugins; - - private IManagedPlugin[] _loadedPlugins = []; - - /// - /// Configures the manager to capture and manage plugins within a plugin stack - /// - /// - /// - /// - public void LoadPlugins(ILogProvider debugLog) - { - _ = stack ?? throw new InvalidOperationException("Plugin stack has not been set."); - - Check(); - - //Initialize the plugin stack and store the loaded plugins - _loadedPlugins = stack.InitializePluginStack(debugLog); - - debugLog.Information("Plugin loading completed"); - } - - - /// - public bool SendCommandToPlugin(string pluginName, string message, StringComparison nameComparison = StringComparison.Ordinal) - { - Check(); - - foreach(IManagedPlugin plugin in _loadedPlugins) - { - if(plugin.SendCommandToPlugin(pluginName, message, nameComparison)) - { - return true; - } - } - - return false; - } - - /// - public void ForceReloadAllPlugins() - { - Check(); - - //Reload all plugins, causing an event cascade - stack.ReloadPlugins(); - } - - /// - public void UnloadPlugins() - { - Check(); - - //Unload all plugin controllers - stack.UnloadPlugins(); - - /* - * All plugin instances must be destroyed because the - * only way they will be loaded is from their files - * again, so they must be released - */ - Free(); - } - - protected override void Free() - { - //Clear plugin table - _loadedPlugins = []; - - //Dispose the plugin stack - stack.Dispose(); - } - } -} diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginRutimeEventHandler.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginRutimeEventHandler.cs deleted file mode 100644 index f892823..0000000 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginRutimeEventHandler.cs +++ /dev/null @@ -1,80 +0,0 @@ -/* -* Copyright (c) 2024 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.ServiceStack -* File: PluginRutimeEventHandler.cs -* -* PluginRutimeEventHandler.cs is part of VNLib.Plugins.Essentials.ServiceStack which -* is part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.ServiceStack 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 2 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.ServiceStack 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.Linq; - -using VNLib.Utils.Extensions; -using VNLib.Plugins.Runtime; - -namespace VNLib.Plugins.Essentials.ServiceStack -{ - internal sealed class PluginRutimeEventHandler(ServiceDomain Domain) : IPluginEventListener - { - /// - void IPluginEventListener.OnPluginLoaded(PluginController controller, object? state) => OnPluginLoaded((state as IManagedPlugin)!); - - /// - void IPluginEventListener.OnPluginUnloaded(PluginController controller, object? state) => OnPluginUnloaded((state as IManagedPlugin)!); - - /// - /// Called when a plugin has been successfully loaded and - /// should be put into service - /// - /// The plugin that was loaded - internal void OnPluginLoaded(IManagedPlugin plugin) - { - //Run onload method before invoking other handlers - plugin.OnPluginLoaded(); - - //Get event listeners at event time because deps may be modified by the domain - ServiceGroup[] deps = Domain.ServiceGroups.Select(static d => d).ToArray(); - - //run onload method - deps.TryForeach(d => d.OnPluginLoaded(plugin)); - } - - /// - /// Called when a plugin is about to be unloaded and should - /// be removed from service. - /// - /// The plugin instance to unload - internal void OnPluginUnloaded(IManagedPlugin plugin) - { - try - { - //Get event listeners at event time because deps may be modified by the domain - ServiceGroup[] deps = Domain.ServiceGroups.Select(static d => d).ToArray(); - - //Run unloaded method - deps.TryForeach(d => d.OnPluginUnloaded(plugin)); - } - finally - { - //always unload the plugin wrapper - plugin.OnPluginUnloaded(); - } - } - } -} diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs deleted file mode 100644 index 5f4e6e0..0000000 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs +++ /dev/null @@ -1,327 +0,0 @@ -/* -* Copyright (c) 2024 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.ServiceStack -* File: PluginStackInitializer.cs -* -* PluginStackInitializer.cs is part of VNLib.Plugins.Essentials.ServiceStack which -* is part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.ServiceStack 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 2 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.ServiceStack 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.Linq; -using System.Diagnostics; -using System.Threading.Tasks; -using System.Collections.Generic; -using System.ComponentModel.Design; - -using VNLib.Utils.Logging; -using VNLib.Plugins.Runtime; -using VNLib.Utils.Extensions; -using VNLib.Plugins.Runtime.Services; - -namespace VNLib.Plugins.Essentials.ServiceStack -{ - - internal sealed class PluginStackInitializer(PluginRutimeEventHandler Listener, IPluginStack Stack, IManualPlugin[] ManualPlugins, bool ConcurrentLoad) - : IPluginInitializer - { - private readonly LinkedList _managedPlugins = new(); - private readonly LinkedList _manualPlugins = new(); - - private void PrepareStack() - { - /* - * Since we own the plugin stack, it is safe to build it here. - * This method is not public and should not be called more than - * once. Otherwise it can cause issues with the plugin stack. - */ - Stack.BuildStack(); - - //Create plugin wrappers from loaded plugins - ManagedPlugin[] wrapper = Stack.Plugins.Select(static p => new ManagedPlugin(p)).ToArray(); - - //Add wrappers to list of managed plugins - Array.ForEach(wrapper, p => _managedPlugins.AddLast(p)); - - //Register for all plugins and pass the plugin instance as the state object - Array.ForEach(wrapper, p => p.Plugin.Controller.Register(Listener, p)); - - //Add manual plugins to list of managed plugins - Array.ForEach(ManualPlugins, p => _manualPlugins.AddLast(new ManualPluginWrapper(Listener, p))); - } - - /// - public IManagedPlugin[] InitializePluginStack(ILogProvider debugLog) - { - //Prepare the plugin stack before initializing - PrepareStack(); - - //single thread initialziation - LinkedList _loadedPlugins = new(); - - //Combine all managed plugins and initialize them individually - IEnumerable plugins = _managedPlugins.Union(_manualPlugins); - - foreach(IManagedPlugin p in plugins) - { - //Try init plugin and add it to the list of loaded plugins - if (InitializePluginCore(p, debugLog)) - { - _loadedPlugins.AddLast(p); - } - } - - /* - * Load stage, load only initialized plugins. - * - * Optionally single-threaded or parallel - */ - - if (ConcurrentLoad) - { - Parallel.ForEach(_loadedPlugins, wp => LoadPlugin(wp, debugLog)); - } - else - { - _loadedPlugins.TryForeach(_loadedPlugins => LoadPlugin(_loadedPlugins, debugLog)); - } - - return [.. _loadedPlugins]; - } - - /// - public void UnloadPlugins() - { - Stack.UnloadAll(); - - //Unload manual plugins in listener - _manualPlugins.TryForeach(static mp => mp.Unload()); - } - - /// - public void ReloadPlugins() - { - Stack.ReloadAll(); - - //Unload manual plugins in listener - _manualPlugins.TryForeach(static mp => mp.Unload()); - - //Load, then invoke on-loaded events - _manualPlugins.TryForeach(static mp => mp.Load()); - } - - /// - public void Dispose() - { - Stack.Dispose(); - _manualPlugins.TryForeach(static mp => mp.Dispose()); - _manualPlugins.Clear(); - } - - private static bool InitializePluginCore(IManagedPlugin plugin, ILogProvider debugLog) - { - try - { - if (plugin is ManagedPlugin mp) - { - //Initialzie plugin wrapper - mp.Plugin.InitializeController(); - - /* - * If the plugin assembly does not expose any plugin types or there is an issue loading the assembly, - * its types my not unify, then we should give the user feedback insead of a silent fail. - */ - if (!mp.Plugin.Controller.Plugins.Any()) - { - debugLog.Warn("No plugin instances were exposed via {asm} assembly. This may be due to an assebmly mismatch", plugin.ToString()); - } - } - else if(plugin is ManualPluginWrapper mpw) - { - //Initialzie plugin wrapper - mpw.Plugin.Initialize(); - } - else - { - Debug.Fail("Missed managed plugin wrapper type"); - } - - return true; - } - catch (Exception ex) - { - debugLog.Error("Exception raised during initialzation of {asm}. It has been removed from the collection\n{ex}", plugin.ToString(), ex); - } - - return false; - } - - private void LoadPlugin(IManagedPlugin plugin, ILogProvider debugLog) - { - Stopwatch sw = new(); - try - { - sw.Start(); - - //Recover the base class used to load instances - if (plugin is ManagedPlugin mp) - { - mp.Plugin.LoadPlugins(); - } - else if (plugin is ManualPluginWrapper mpw) - { - mpw.Load(); - } - else - { - Debug.Fail("Missed managed plugin wrapper type"); - } - - sw.Stop(); - - debugLog.Verbose("Loaded {pl} in {tm} ms", plugin.ToString(), sw.ElapsedMilliseconds); - } - catch (Exception ex) - { - debugLog.Error("Exception raised during loading {asf}. Failed to load plugin \n{ex}", plugin.ToString(), ex); - } - finally - { - sw.Stop(); - } - } - - - private sealed record class ManagedPlugin(RuntimePluginLoader Plugin) : IManagedPlugin - { - private ServiceContainer? _services; - - /// - public IServiceContainer Services => _services ?? throw new InvalidOperationException("The service container is not currently loaded"); - - /* - * Automatically called after the plugin has successfully loaded - * by event handlers below - */ - - /// - void IManagedPlugin.OnPluginLoaded() - { - //If the service container is defined, dispose - _services?.Dispose(); - - //Init new service container - _services = new(); - - //Get all exported services and add them to the container - PluginServiceExport[] exports = Plugin.Controller.GetExportedServices(); - Array.ForEach(exports, e => _services.AddService(e.ServiceType, e.Service, true)); - } - - /// - void IManagedPlugin.OnPluginUnloaded() - { - //Cleanup services no longer in use. Plugin is still valid until this method returns - _services?.Dispose(); - - //Remove ref to services - _services = null; - } - - /// - bool IManagedPlugin.SendCommandToPlugin(string pluginName, string command, StringComparison comp) - { - //Get plugin - LivePlugin? plugin = Plugin.Controller.Plugins.FirstOrDefault(p => p.PluginName.Equals(pluginName, comp)); - - //If plugin is null, return false - if (plugin == null) - { - return false; - } - - return plugin.SendConsoleMessage(command); - } - - public override string ToString() => Path.GetFileName(Plugin.Config.AssemblyFile); - } - - private sealed record class ManualPluginWrapper(PluginRutimeEventHandler Listener, IManualPlugin Plugin) : IManagedPlugin, IDisposable - { - private ServiceContainer _container = new(); - - /// - public IServiceContainer Services => _container; - - public void Load() - { - Plugin.Load(); - Plugin.GetAllExportedServices(_container); - - //Finally notify of load - Listener.OnPluginLoaded(this); - } - - public void Unload() - { - //Notify of unload - Listener.OnPluginUnloaded(this); - - Plugin.Unload(); - - //Unload and re-init container - _container.Dispose(); - _container = new(); - } - - public void Dispose() - { - //Dispose container - _container.Dispose(); - - //Dispose plugin - Plugin.Dispose(); - } - - /// - bool IManagedPlugin.SendCommandToPlugin(string pluginName, string command, StringComparison comp) - { - - if (Plugin.Name.Equals(pluginName, comp)) - { - Plugin.OnConsoleCommand(command); - return true; - } - - return false; - } - - void IManagedPlugin.OnPluginLoaded() - { } - - void IManagedPlugin.OnPluginUnloaded() - { } - - /// - public override string ToString() => Plugin.Name; - } - - } -} diff --git a/lib/Plugins.Essentials.ServiceStack/src/Plugins/IHttpPluginManager.cs b/lib/Plugins.Essentials.ServiceStack/src/Plugins/IHttpPluginManager.cs new file mode 100644 index 0000000..23e436b --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/Plugins/IHttpPluginManager.cs @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: IHttpPluginManager.cs +* +* IHttpPluginManager.cs is part of VNLib.Plugins.Essentials.ServiceStack which +* is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack 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 2 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.ServiceStack 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.Collections.Generic; + +namespace VNLib.Plugins.Essentials.ServiceStack.Plugins +{ + /// + /// Represents a live plugin controller that manages all + /// plugins loaded in a + /// + public interface IHttpPluginManager + { + /// + /// The the plugins managed by this + /// + public IEnumerable Plugins { get; } + + /// + /// Sends a message to a plugin identified by it's name. + /// + /// The name of the plugin to pass the message to + /// The message to pass to the plugin + /// The name string comparison type + /// True if the plugin was found and it has a message handler loaded + /// + bool SendCommandToPlugin(string pluginName, string message, StringComparison nameComparison = StringComparison.Ordinal); + + /// + /// Manually reloads all plugins loaded to the current service manager + /// + /// + /// + void ForceReloadAllPlugins(); + + /// + /// Unloads all loaded plugins and calls thier event handlers + /// + void UnloadPlugins(); + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/Plugins/IManagedPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/Plugins/IManagedPlugin.cs new file mode 100644 index 0000000..b2506c5 --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/Plugins/IManagedPlugin.cs @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: IManagedPlugin.cs +* +* IManagedPlugin.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack 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 2 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.ServiceStack 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.Design; + +namespace VNLib.Plugins.Essentials.ServiceStack.Plugins +{ + /// + /// Represents a plugin managed by a that includes dynamically loaded plugins + /// + public interface IManagedPlugin + { + /// + /// The exposed services the inernal plugin provides + /// + /// + /// WARNING: Services exposed by the plugin will abide by the plugin lifecycle, so consumers + /// must listen for plugin load/unload events to respect lifecycles properly. + /// + IServiceContainer Services { get; } + + /// + /// Internal notification that the plugin is loaded + /// + internal void OnPluginLoaded(); + + /// + /// Internal notification that the plugin is unloaded + /// + internal void OnPluginUnloaded(); + + /// + /// Sends the specified command to the desired plugin by it's name + /// + /// The name of the plugin to find + /// The command text to send to the plugin + /// The string name comparison type + /// True if the command was sent successfully + internal bool SendCommandToPlugin(string pluginName, string command, StringComparison comp); + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/Plugins/IManualPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/Plugins/IManualPlugin.cs new file mode 100644 index 0000000..57fd631 --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/Plugins/IManualPlugin.cs @@ -0,0 +1,71 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: IManualPlugin.cs +* +* IManualPlugin.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack 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 2 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.ServiceStack 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.Design; + +namespace VNLib.Plugins.Essentials.ServiceStack.Plugins +{ + /// + /// Represents a plugin that may be added to a service stack in user-code + /// instead of the conventional runtime plugin loading system + /// + public interface IManualPlugin : IDisposable + { + /// + /// The name of the plugin + /// + string Name { get; } + + /// + /// Collects all exported services for use within the service stack + /// + /// The container to add services to + void GetAllExportedServices(IServiceContainer container); + + /// + /// Initializes the plugin, called before accessing any other methods + /// + void Initialize(); + + /// + /// Loads the plugin, called after initialization but before getting + /// endpoints or services to allow for the plugin to configure itself + /// and perform initial setup + /// + void Load(); + + /// + /// Called when an unload was requested, either manually by the plugin controller + /// or when the service stack is unloading + /// + void Unload(); + + /// + /// Passes a console command to the plugin + /// + /// The raw command text to pass to the plugin from the console + void OnConsoleCommand(string command); + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/Plugins/IPluginInitializer.cs b/lib/Plugins.Essentials.ServiceStack/src/Plugins/IPluginInitializer.cs new file mode 100644 index 0000000..9435d9b --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/Plugins/IPluginInitializer.cs @@ -0,0 +1,40 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: IPluginInitializer.cs +* +* IPluginInitializer.cs is part of VNLib.Plugins.Essentials.ServiceStack which +* is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack 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 2 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.ServiceStack 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.Utils.Logging; + +namespace VNLib.Plugins.Essentials.ServiceStack.Plugins +{ + internal interface IPluginInitializer + { + IManagedPlugin[] InitializePluginStack(ILogProvider eventLogger); + + void UnloadPlugins(); + + void ReloadPlugins(); + + void Dispose(); + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginExtensions.cs b/lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginExtensions.cs new file mode 100644 index 0000000..4d04c54 --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginExtensions.cs @@ -0,0 +1,57 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: PluginExtensions.cs +* +* PluginExtensions.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack 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 2 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.ServiceStack 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.Linq; +using System.Collections.Generic; + +using VNLib.Plugins.Essentials.Runtime; + +namespace VNLib.Plugins.Essentials.ServiceStack.Plugins +{ + /// + /// Internal and service stack specific extensions for plugins + /// + public static class PluginExtensions + { + /// + /// Gets the endpoints exposed by the plugin + /// + /// + /// The enumeration of web endpoints + internal static IEnumerable GetEndpoints(this IManagedPlugin plugin) + { + //Try to get the endpoint defintion + if (plugin.Services.GetService(typeof(IVirtualEndpointDefinition)) is IVirtualEndpointDefinition defintion) + { + //Return the endpoints from the definition + return defintion.GetEndpoints(); + } + + //If the plugin does not have an endpoint definition, return an empty enumeration + return Enumerable.Empty(); + } + + internal static PluginRutimeEventHandler GetListener(this ServiceDomain domain) => new(domain); + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginManager.cs b/lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginManager.cs new file mode 100644 index 0000000..ce80a9e --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginManager.cs @@ -0,0 +1,118 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: PluginManager.cs +* +* PluginManager.cs is part of VNLib.Plugins.Essentials.ServiceStack which +* is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack 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 2 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.ServiceStack 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.Collections.Generic; + +using VNLib.Utils; +using VNLib.Utils.Logging; + +namespace VNLib.Plugins.Essentials.ServiceStack.Plugins +{ + + /// + /// A sealed type that manages the plugin interaction layer. Manages the lifetime of plugin + /// instances, exposes controls, and relays stateful plugin events. + /// + internal sealed class PluginManager(IPluginInitializer stack) : VnDisposeable, IHttpPluginManager + { + + /// + /// The collection of internal controllers + /// + public IEnumerable Plugins => _loadedPlugins; + + private IManagedPlugin[] _loadedPlugins = []; + + /// + /// Configures the manager to capture and manage plugins within a plugin stack + /// + /// + /// + /// + public void LoadPlugins(ILogProvider debugLog) + { + _ = stack ?? throw new InvalidOperationException("Plugin stack has not been set."); + + Check(); + + //Initialize the plugin stack and store the loaded plugins + _loadedPlugins = stack.InitializePluginStack(debugLog); + + debugLog.Information("Plugin loading completed"); + } + + + /// + public bool SendCommandToPlugin(string pluginName, string message, StringComparison nameComparison = StringComparison.Ordinal) + { + Check(); + + foreach (IManagedPlugin plugin in _loadedPlugins) + { + if (plugin.SendCommandToPlugin(pluginName, message, nameComparison)) + { + return true; + } + } + + return false; + } + + /// + public void ForceReloadAllPlugins() + { + Check(); + + //Reload all plugins, causing an event cascade + stack.ReloadPlugins(); + } + + /// + public void UnloadPlugins() + { + Check(); + + //Unload all plugin controllers + stack.UnloadPlugins(); + + /* + * All plugin instances must be destroyed because the + * only way they will be loaded is from their files + * again, so they must be released + */ + Free(); + } + + protected override void Free() + { + //Clear plugin table + _loadedPlugins = []; + + //Dispose the plugin stack + stack.Dispose(); + } + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginRutimeEventHandler.cs b/lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginRutimeEventHandler.cs new file mode 100644 index 0000000..c5b094f --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginRutimeEventHandler.cs @@ -0,0 +1,80 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: PluginRutimeEventHandler.cs +* +* PluginRutimeEventHandler.cs is part of VNLib.Plugins.Essentials.ServiceStack which +* is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack 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 2 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.ServiceStack 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.Linq; + +using VNLib.Utils.Extensions; +using VNLib.Plugins.Runtime; + +namespace VNLib.Plugins.Essentials.ServiceStack.Plugins +{ + internal sealed class PluginRutimeEventHandler(ServiceDomain Domain) : IPluginEventListener + { + /// + void IPluginEventListener.OnPluginLoaded(PluginController controller, object? state) => OnPluginLoaded((state as IManagedPlugin)!); + + /// + void IPluginEventListener.OnPluginUnloaded(PluginController controller, object? state) => OnPluginUnloaded((state as IManagedPlugin)!); + + /// + /// Called when a plugin has been successfully loaded and + /// should be put into service + /// + /// The plugin that was loaded + internal void OnPluginLoaded(IManagedPlugin plugin) + { + //Run onload method before invoking other handlers + plugin.OnPluginLoaded(); + + //Get event listeners at event time because deps may be modified by the domain + ServiceGroup[] deps = Domain.ServiceGroups.Select(static d => d).ToArray(); + + //run onload method + deps.TryForeach(d => d.OnPluginLoaded(plugin)); + } + + /// + /// Called when a plugin is about to be unloaded and should + /// be removed from service. + /// + /// The plugin instance to unload + internal void OnPluginUnloaded(IManagedPlugin plugin) + { + try + { + //Get event listeners at event time because deps may be modified by the domain + ServiceGroup[] deps = Domain.ServiceGroups.Select(static d => d).ToArray(); + + //Run unloaded method + deps.TryForeach(d => d.OnPluginUnloaded(plugin)); + } + finally + { + //always unload the plugin wrapper + plugin.OnPluginUnloaded(); + } + } + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginStackInitializer.cs b/lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginStackInitializer.cs new file mode 100644 index 0000000..fb685f7 --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/Plugins/PluginStackInitializer.cs @@ -0,0 +1,327 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: PluginStackInitializer.cs +* +* PluginStackInitializer.cs is part of VNLib.Plugins.Essentials.ServiceStack which +* is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack 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 2 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.ServiceStack 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.Linq; +using System.Diagnostics; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.ComponentModel.Design; + +using VNLib.Utils.Logging; +using VNLib.Plugins.Runtime; +using VNLib.Utils.Extensions; +using VNLib.Plugins.Runtime.Services; + +namespace VNLib.Plugins.Essentials.ServiceStack.Plugins +{ + + internal sealed class PluginStackInitializer(PluginRutimeEventHandler Listener, IPluginStack Stack, IManualPlugin[] ManualPlugins, bool ConcurrentLoad) + : IPluginInitializer + { + private readonly LinkedList _managedPlugins = new(); + private readonly LinkedList _manualPlugins = new(); + + private void PrepareStack() + { + /* + * Since we own the plugin stack, it is safe to build it here. + * This method is not public and should not be called more than + * once. Otherwise it can cause issues with the plugin stack. + */ + Stack.BuildStack(); + + //Create plugin wrappers from loaded plugins + ManagedPlugin[] wrapper = Stack.Plugins.Select(static p => new ManagedPlugin(p)).ToArray(); + + //Add wrappers to list of managed plugins + Array.ForEach(wrapper, p => _managedPlugins.AddLast(p)); + + //Register for all plugins and pass the plugin instance as the state object + Array.ForEach(wrapper, p => p.Plugin.Controller.Register(Listener, p)); + + //Add manual plugins to list of managed plugins + Array.ForEach(ManualPlugins, p => _manualPlugins.AddLast(new ManualPluginWrapper(Listener, p))); + } + + /// + public IManagedPlugin[] InitializePluginStack(ILogProvider debugLog) + { + //Prepare the plugin stack before initializing + PrepareStack(); + + //single thread initialziation + LinkedList _loadedPlugins = new(); + + //Combine all managed plugins and initialize them individually + IEnumerable plugins = _managedPlugins.Union(_manualPlugins); + + foreach (IManagedPlugin p in plugins) + { + //Try init plugin and add it to the list of loaded plugins + if (InitializePluginCore(p, debugLog)) + { + _loadedPlugins.AddLast(p); + } + } + + /* + * Load stage, load only initialized plugins. + * + * Optionally single-threaded or parallel + */ + + if (ConcurrentLoad) + { + Parallel.ForEach(_loadedPlugins, wp => LoadPlugin(wp, debugLog)); + } + else + { + _loadedPlugins.TryForeach(_loadedPlugins => LoadPlugin(_loadedPlugins, debugLog)); + } + + return [.. _loadedPlugins]; + } + + /// + public void UnloadPlugins() + { + Stack.UnloadAll(); + + //Unload manual plugins in listener + _manualPlugins.TryForeach(static mp => mp.Unload()); + } + + /// + public void ReloadPlugins() + { + Stack.ReloadAll(); + + //Unload manual plugins in listener + _manualPlugins.TryForeach(static mp => mp.Unload()); + + //Load, then invoke on-loaded events + _manualPlugins.TryForeach(static mp => mp.Load()); + } + + /// + public void Dispose() + { + Stack.Dispose(); + _manualPlugins.TryForeach(static mp => mp.Dispose()); + _manualPlugins.Clear(); + } + + private static bool InitializePluginCore(IManagedPlugin plugin, ILogProvider debugLog) + { + try + { + if (plugin is ManagedPlugin mp) + { + //Initialzie plugin wrapper + mp.Plugin.InitializeController(); + + /* + * If the plugin assembly does not expose any plugin types or there is an issue loading the assembly, + * its types my not unify, then we should give the user feedback insead of a silent fail. + */ + if (!mp.Plugin.Controller.Plugins.Any()) + { + debugLog.Warn("No plugin instances were exposed via {asm} assembly. This may be due to an assebmly mismatch", plugin.ToString()); + } + } + else if (plugin is ManualPluginWrapper mpw) + { + //Initialzie plugin wrapper + mpw.Plugin.Initialize(); + } + else + { + Debug.Fail("Missed managed plugin wrapper type"); + } + + return true; + } + catch (Exception ex) + { + debugLog.Error("Exception raised during initialzation of {asm}. It has been removed from the collection\n{ex}", plugin.ToString(), ex); + } + + return false; + } + + private void LoadPlugin(IManagedPlugin plugin, ILogProvider debugLog) + { + Stopwatch sw = new(); + try + { + sw.Start(); + + //Recover the base class used to load instances + if (plugin is ManagedPlugin mp) + { + mp.Plugin.LoadPlugins(); + } + else if (plugin is ManualPluginWrapper mpw) + { + mpw.Load(); + } + else + { + Debug.Fail("Missed managed plugin wrapper type"); + } + + sw.Stop(); + + debugLog.Verbose("Loaded {pl} in {tm} ms", plugin.ToString(), sw.ElapsedMilliseconds); + } + catch (Exception ex) + { + debugLog.Error("Exception raised during loading {asf}. Failed to load plugin \n{ex}", plugin.ToString(), ex); + } + finally + { + sw.Stop(); + } + } + + + private sealed record class ManagedPlugin(RuntimePluginLoader Plugin) : IManagedPlugin + { + private ServiceContainer? _services; + + /// + public IServiceContainer Services => _services ?? throw new InvalidOperationException("The service container is not currently loaded"); + + /* + * Automatically called after the plugin has successfully loaded + * by event handlers below + */ + + /// + void IManagedPlugin.OnPluginLoaded() + { + //If the service container is defined, dispose + _services?.Dispose(); + + //Init new service container + _services = new(); + + //Get all exported services and add them to the container + PluginServiceExport[] exports = Plugin.Controller.GetExportedServices(); + Array.ForEach(exports, e => _services.AddService(e.ServiceType, e.Service, true)); + } + + /// + void IManagedPlugin.OnPluginUnloaded() + { + //Cleanup services no longer in use. Plugin is still valid until this method returns + _services?.Dispose(); + + //Remove ref to services + _services = null; + } + + /// + bool IManagedPlugin.SendCommandToPlugin(string pluginName, string command, StringComparison comp) + { + //Get plugin + LivePlugin? plugin = Plugin.Controller.Plugins.FirstOrDefault(p => p.PluginName.Equals(pluginName, comp)); + + //If plugin is null, return false + if (plugin == null) + { + return false; + } + + return plugin.SendConsoleMessage(command); + } + + public override string ToString() => Path.GetFileName(Plugin.Config.AssemblyFile); + } + + private sealed record class ManualPluginWrapper(PluginRutimeEventHandler Listener, IManualPlugin Plugin) : IManagedPlugin, IDisposable + { + private ServiceContainer _container = new(); + + /// + public IServiceContainer Services => _container; + + public void Load() + { + Plugin.Load(); + Plugin.GetAllExportedServices(_container); + + //Finally notify of load + Listener.OnPluginLoaded(this); + } + + public void Unload() + { + //Notify of unload + Listener.OnPluginUnloaded(this); + + Plugin.Unload(); + + //Unload and re-init container + _container.Dispose(); + _container = new(); + } + + public void Dispose() + { + //Dispose container + _container.Dispose(); + + //Dispose plugin + Plugin.Dispose(); + } + + /// + bool IManagedPlugin.SendCommandToPlugin(string pluginName, string command, StringComparison comp) + { + + if (Plugin.Name.Equals(pluginName, comp)) + { + Plugin.OnConsoleCommand(command); + return true; + } + + return false; + } + + void IManagedPlugin.OnPluginLoaded() + { } + + void IManagedPlugin.OnPluginUnloaded() + { } + + /// + public override string ToString() => Plugin.Name; + } + + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/ServiceDomain.cs b/lib/Plugins.Essentials.ServiceStack/src/ServiceDomain.cs index 35bf65b..52e4984 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/ServiceDomain.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/ServiceDomain.cs @@ -23,11 +23,10 @@ */ using System; -using System.Net; -using System.Linq; using System.Collections.Generic; using VNLib.Utils.Extensions; +using VNLib.Plugins.Essentials.ServiceStack.Construction; namespace VNLib.Plugins.Essentials.ServiceStack { @@ -38,7 +37,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// public sealed class ServiceDomain { - private readonly LinkedList _serviceGroups = new(); + private ServiceGroup[] _serviceGroups = []; /// /// Gets all service groups loaded in the service manager @@ -51,15 +50,11 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// /// The callback method to build virtual hosts /// A value that indicates if any virtual hosts were successfully loaded - public bool BuildDomain(Action> hostBuilder) + public void BuildDomain(ServiceBuilder hostBuilder) { - //LL to store created hosts - LinkedList hosts = new(); + ArgumentNullException.ThrowIfNull(hostBuilder); - //build hosts - hostBuilder.Invoke(hosts); - - return FromExisting(hosts); + FromExisting(hostBuilder.BuildGroups()); } /// @@ -67,36 +62,11 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// /// The enumeration of virtual hosts /// A value that indicates if any virtual hosts were successfully loaded - public bool FromExisting(IEnumerable hosts) - { - //Get service groups and pass service group list - CreateServiceGroups(_serviceGroups, hosts); - return _serviceGroups.Any(); - } - - private static void CreateServiceGroups(ICollection groups, IEnumerable hosts) + public void FromExisting(IEnumerable hosts) { - //Get distinct interfaces - IPEndPoint[] interfaces = hosts.Select(static s => s.TransportInfo.TransportEndpoint).Distinct().ToArray(); - - //Select hosts of the same interface to create a group from - foreach (IPEndPoint iface in interfaces) - { - IEnumerable groupHosts = hosts.Where(host => host.TransportInfo.TransportEndpoint.Equals(iface)); + ArgumentNullException.ThrowIfNull(hosts); - //Find any duplicate hostnames for the same service gorup - IServiceHost[] overlap = groupHosts.Where(vh => groupHosts.Select(static s => s.Processor.Hostname).Count(hostname => vh.Processor.Hostname == hostname) > 1).ToArray(); - - if(overlap.Length > 0) - { - throw new ArgumentException($"The hostname '{overlap.Last().Processor.Hostname}' is already in use by another virtual host"); - } - - //init new service group around an interface and its roots - ServiceGroup group = new(iface, groupHosts); - - groups.Add(group); - } + hosts.ForEach(h => _serviceGroups = [.. _serviceGroups, h]); } /// @@ -106,9 +76,11 @@ namespace VNLib.Plugins.Essentials.ServiceStack public void TearDown() { //Manually cleanup if unload missed data - _serviceGroups.TryForeach(static sg => sg.UnloadAll()); - //empty service groups - _serviceGroups.Clear(); + Array.ForEach(_serviceGroups, static sg => sg.UnloadAll()); + + Array.Clear(_serviceGroups); + + _serviceGroups = []; } } } diff --git a/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs b/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs index 29b9fdc..a446e37 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs @@ -28,6 +28,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using VNLib.Utils.Extensions; +using VNLib.Plugins.Essentials.ServiceStack.Plugins; namespace VNLib.Plugins.Essentials.ServiceStack { @@ -41,18 +42,12 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// Initalizes a new of virtual hosts /// with common transport /// - /// The to listen for connections on /// The hosts that share a common interface endpoint - public sealed class ServiceGroup(IPEndPoint serviceEndpoint, IEnumerable hosts) + public sealed class ServiceGroup(IEnumerable hosts) { private readonly LinkedList _vHosts = new(hosts); private readonly ConditionalWeakTable _endpointsForPlugins = new(); - /// - /// The transport endpoint for all loaded service hosts - /// - public IPEndPoint ServiceEndpoint => serviceEndpoint; - /// /// The collection of hosts that are loaded by this group /// -- cgit