From d73312c4f42d5bf0db0528baa0cabc40d8c48fc9 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 16 Oct 2023 21:29:37 +0200 Subject: [PATCH] Web server - Ensure controllers and web modules provided by plugins are removed on disable --- .../WebServer/Interfaces/IWebServerService.cs | 16 ++--- .../WebServer/WebApiControllerRegistration.cs | 45 +++++++++++--- .../WebServer/WebModuleRegistration.cs | 37 +++++++++-- .../Services/WebServer/WebServerService.cs | 61 +++++++++++-------- 4 files changed, 109 insertions(+), 50 deletions(-) diff --git a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs index 5cfafa6a5..b950f9f49 100644 --- a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs @@ -95,35 +95,29 @@ public interface IWebServerService : IArtemisService /// Adds a new Web API controller and restarts the web server /// /// The type of Web API controller to remove - void AddController(PluginFeature feature) where T : WebApiController; + WebApiControllerRegistration AddController(PluginFeature feature) where T : WebApiController; /// /// Removes an existing Web API controller and restarts the web server /// /// The type of Web API controller to remove - void RemoveController() where T : WebApiController; + void RemoveController(WebApiControllerRegistration registration); /// /// Adds a new EmbedIO module and restarts the web server /// - void AddModule(PluginFeature feature, Func create); + WebModuleRegistration AddModule(PluginFeature feature, Func create); /// /// Removes a EmbedIO module and restarts the web server /// - void RemoveModule(Func create); + void RemoveModule(WebModuleRegistration create); /// /// Adds a new EmbedIO module and restarts the web server /// /// The type of module to add - void AddModule(PluginFeature feature) where T : IWebModule; - - /// - /// Removes a EmbedIO module and restarts the web server - /// - /// The type of module to remove - void RemoveModule() where T : IWebModule; + WebModuleRegistration AddModule(PluginFeature feature) where T : IWebModule; /// /// Occurs when the web server has been created and is about to start. This is the ideal place to add your own modules. diff --git a/src/Artemis.Core/Services/WebServer/WebApiControllerRegistration.cs b/src/Artemis.Core/Services/WebServer/WebApiControllerRegistration.cs index 1a6375a11..4a2c7bb50 100644 --- a/src/Artemis.Core/Services/WebServer/WebApiControllerRegistration.cs +++ b/src/Artemis.Core/Services/WebServer/WebApiControllerRegistration.cs @@ -3,26 +3,55 @@ using EmbedIO.WebApi; namespace Artemis.Core.Services; -internal class WebApiControllerRegistration : WebApiControllerRegistration where T : WebApiController +/// +/// Represents a web API controller registration. +/// +/// The type of the web API controller. +public class WebApiControllerRegistration : WebApiControllerRegistration where T : WebApiController { - public WebApiControllerRegistration(PluginFeature feature) : base(feature, typeof(T)) + internal WebApiControllerRegistration(IWebServerService webServerService, PluginFeature feature) : base(webServerService, feature, typeof(T)) { Factory = () => feature.Plugin.Resolve(); } - public Func Factory { get; set; } - public override object UntypedFactory => Factory; + internal Func Factory { get; set; } + internal override object UntypedFactory => Factory; } -internal abstract class WebApiControllerRegistration +/// +/// Represents a web API controller registration. +/// +public abstract class WebApiControllerRegistration { - protected WebApiControllerRegistration(PluginFeature feature, Type controllerType) + private readonly IWebServerService _webServerService; + + /// + /// Creates a new instance of the class. + /// + protected internal WebApiControllerRegistration(IWebServerService webServerService, PluginFeature feature, Type controllerType) { + _webServerService = webServerService; Feature = feature; ControllerType = controllerType; + + Feature.Disabled += FeatureOnDisabled; } - public abstract object UntypedFactory { get; } - public Type ControllerType { get; set; } + private void FeatureOnDisabled(object? sender, EventArgs e) + { + _webServerService.RemoveController(this); + Feature.Disabled -= FeatureOnDisabled; + } + + internal abstract object UntypedFactory { get; } + + /// + /// Gets the type of the web API controller. + /// + public Type ControllerType { get; } + + /// + /// Gets the plugin feature that provided the web API controller. + /// public PluginFeature Feature { get; } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/WebModuleRegistration.cs b/src/Artemis.Core/Services/WebServer/WebModuleRegistration.cs index 270a5ec4d..dca340b35 100644 --- a/src/Artemis.Core/Services/WebServer/WebModuleRegistration.cs +++ b/src/Artemis.Core/Services/WebServer/WebModuleRegistration.cs @@ -3,25 +3,44 @@ using EmbedIO; namespace Artemis.Core.Services; -internal class WebModuleRegistration +/// +/// Represents a registration for a web module. +/// +public class WebModuleRegistration { - public WebModuleRegistration(PluginFeature feature, Type webModuleType) + private readonly IWebServerService _webServerService; + + internal WebModuleRegistration(IWebServerService webServerService, PluginFeature feature, Type webModuleType) { + _webServerService = webServerService; Feature = feature ?? throw new ArgumentNullException(nameof(feature)); WebModuleType = webModuleType ?? throw new ArgumentNullException(nameof(webModuleType)); + + Feature.Disabled += FeatureOnDisabled; } - public WebModuleRegistration(PluginFeature feature, Func create) + internal WebModuleRegistration(IWebServerService webServerService, PluginFeature feature, Func create) { + _webServerService = webServerService; Feature = feature ?? throw new ArgumentNullException(nameof(feature)); Create = create ?? throw new ArgumentNullException(nameof(create)); + + Feature.Disabled += FeatureOnDisabled; } + /// + /// The plugin feature that provided the web module. + /// public PluginFeature Feature { get; } - public Type? WebModuleType { get; } - public Func? Create { get; } - public IWebModule CreateInstance() + /// + /// The type of the web module. + /// + public Type? WebModuleType { get; } + + internal Func? Create { get; } + + internal IWebModule CreateInstance() { if (Create != null) return Create(); @@ -29,4 +48,10 @@ internal class WebModuleRegistration return (IWebModule) Feature.Plugin.Resolve(WebModuleType); throw new ArtemisCoreException("WebModuleRegistration doesn't have a create function nor a web module type :("); } + + private void FeatureOnDisabled(object? sender, EventArgs e) + { + _webServerService.RemoveModule(this); + Feature.Disabled -= FeatureOnDisabled; + } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs index e5030c9b4..263d95315 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -39,9 +39,9 @@ internal class WebServerService : IWebServerService, IDisposable PluginsModule = new PluginsModule("/plugins"); if (coreService.IsInitialized) - StartWebServer(); + AutoStartWebServer(); else - coreService.Initialized += (_, _) => StartWebServer(); + coreService.Initialized += (sender, args) => AutoStartWebServer(); } public event EventHandler? WebServerStopped; @@ -138,7 +138,7 @@ internal class WebServerService : IWebServerService, IDisposable // Add registered controllers to the API module foreach (WebApiControllerRegistration registration in _controllers) apiModule.RegisterController(registration.ControllerType, (Func) registration.UntypedFactory); - + // Listen for state changes. server.StateChanged += (s, e) => _logger.Verbose("WebServer new state - {state}", e.NewState); @@ -173,6 +173,18 @@ internal class WebServerService : IWebServerService, IDisposable OnWebServerStarted(); } } + + private void AutoStartWebServer() + { + try + { + StartWebServer(); + } + catch (Exception exception) + { + _logger.Warning(exception, "Failed to initially start webserver"); + } + } #endregion @@ -237,10 +249,6 @@ internal class WebServerService : IWebServerService, IDisposable return endPoint; } - private void HandleDataModelRequest(Module module, T value) where T : DataModel, new() - { - } - public void RemovePluginEndPoint(PluginEndPoint endPoint) { PluginsModule.RemovePluginEndPoint(endPoint); @@ -250,15 +258,20 @@ internal class WebServerService : IWebServerService, IDisposable #region Controller management - public void AddController(PluginFeature feature) where T : WebApiController + public WebApiControllerRegistration AddController(PluginFeature feature) where T : WebApiController { - _controllers.Add(new WebApiControllerRegistration(feature)); + if (feature == null) throw new ArgumentNullException(nameof(feature)); + + WebApiControllerRegistration registration = new(this, feature); + _controllers.Add(registration); StartWebServer(); + + return registration; } - public void RemoveController() where T : WebApiController + public void RemoveController(WebApiControllerRegistration registration) { - _controllers.RemoveAll(r => r.ControllerType == typeof(T)); + _controllers.Remove(registration); StartWebServer(); } @@ -266,33 +279,31 @@ internal class WebServerService : IWebServerService, IDisposable #region Module management - public void AddModule(PluginFeature feature, Func create) + public WebModuleRegistration AddModule(PluginFeature feature, Func create) { if (feature == null) throw new ArgumentNullException(nameof(feature)); - _modules.Add(new WebModuleRegistration(feature, create)); + WebModuleRegistration registration = new(this, feature, create); + _modules.Add(registration); StartWebServer(); + + return registration; } - public void RemoveModule(Func create) - { - _modules.RemoveAll(r => r.Create == create); - StartWebServer(); - } - - public void AddModule(PluginFeature feature) where T : IWebModule + public WebModuleRegistration AddModule(PluginFeature feature) where T : IWebModule { if (feature == null) throw new ArgumentNullException(nameof(feature)); - if (_modules.Any(r => r.WebModuleType == typeof(T))) - return; - _modules.Add(new WebModuleRegistration(feature, typeof(T))); + WebModuleRegistration registration = new(this, feature, typeof(T)); + _modules.Add(registration); StartWebServer(); + + return registration; } - public void RemoveModule() where T : IWebModule + public void RemoveModule(WebModuleRegistration registration) { - _modules.RemoveAll(r => r.WebModuleType == typeof(T)); + _modules.Remove(registration); StartWebServer(); }