From ed704a165caf6994b7b12fff4e9cad760e298b5b Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 6 Apr 2021 19:19:19 +0200 Subject: [PATCH] Webserver - Removed '/api/' part from URLs Webserver - Added the ability to register custom EmbedIO modules Webserver - Ensure custom controllers/modules are always cleaned up on plugin feature disable --- .../WebServer/Interfaces/IWebServerService.cs | 20 +++++- .../Services/WebServer/PluginsModule.cs | 8 +-- .../WebServer/WebApiControllerRegistration.cs | 8 ++- .../WebServer/WebModuleRegistration.cs | 20 ++++++ .../Services/WebServer/WebServerService.cs | 62 ++++++++++++++++--- src/Artemis.UI/ApplicationStateManager.cs | 2 +- .../Services/RegistrationService.cs | 2 +- 7 files changed, 102 insertions(+), 20 deletions(-) create mode 100644 src/Artemis.Core/Services/WebServer/WebModuleRegistration.cs diff --git a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs index d88e3da3b..8a20910db 100644 --- a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs @@ -55,7 +55,8 @@ namespace Artemis.Core.Services DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel; /// - /// Adds a new endpoint that directly maps received JSON to the data model of the provided . + /// Adds a new endpoint that directly maps received JSON to the data model of the provided + /// . /// /// The data model type of the module /// The module whose datamodel to apply the received JSON to @@ -64,7 +65,8 @@ namespace Artemis.Core.Services DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(ProfileModule profileModule, string endPointName) where T : DataModel; /// - /// Adds a new endpoint that directly maps received JSON to the data model of the provided . + /// Adds a new endpoint that directly maps received JSON to the data model of the provided + /// . /// /// The data model type of the module /// The data model expansion whose datamodel to apply the received JSON to @@ -114,7 +116,7 @@ namespace Artemis.Core.Services /// Adds a new Web API controller and restarts the web server /// /// The type of Web API controller to remove - void AddController() where T : WebApiController; + void AddController(PluginFeature feature) where T : WebApiController; /// /// Removes an existing Web API controller and restarts the web server @@ -122,6 +124,18 @@ namespace Artemis.Core.Services /// The type of Web API controller to remove void RemoveController() where T : WebApiController; + /// + /// 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; + /// /// 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/PluginsModule.cs b/src/Artemis.Core/Services/WebServer/PluginsModule.cs index d7f9d49de..4ad697b66 100644 --- a/src/Artemis.Core/Services/WebServer/PluginsModule.cs +++ b/src/Artemis.Core/Services/WebServer/PluginsModule.cs @@ -48,13 +48,13 @@ namespace Artemis.Core.Services protected override async Task OnRequestAsync(IHttpContext context) { if (context.Route.SubPath == null) - throw HttpException.NotFound(); + return; // Split the sub path string[] pathParts = context.Route.SubPath.Substring(1).Split('/'); // Expect a plugin ID and an endpoint - if (pathParts == null || pathParts.Length != 2) - throw HttpException.BadRequest("Path must contain a plugin ID and endpoint and nothing else."); + if (pathParts.Length != 2) + return; // Find a matching plugin if (!_pluginEndPoints.TryGetValue(pathParts[0], out Dictionary? endPoints)) @@ -78,7 +78,7 @@ namespace Artemis.Core.Services } /// - public override bool IsFinalHandler => true; + public override bool IsFinalHandler => false; internal string? ServerUrl { get; set; } diff --git a/src/Artemis.Core/Services/WebServer/WebApiControllerRegistration.cs b/src/Artemis.Core/Services/WebServer/WebApiControllerRegistration.cs index ad5fb30dc..f12455c5c 100644 --- a/src/Artemis.Core/Services/WebServer/WebApiControllerRegistration.cs +++ b/src/Artemis.Core/Services/WebServer/WebApiControllerRegistration.cs @@ -6,9 +6,9 @@ namespace Artemis.Core.Services { internal class WebApiControllerRegistration : WebApiControllerRegistration where T : WebApiController { - public WebApiControllerRegistration(IKernel kernel) : base(typeof(T)) + public WebApiControllerRegistration(PluginFeature feature) : base(feature, typeof(T)) { - Factory = () => kernel.Get(); + Factory = () => feature.Plugin.Kernel!.Get(); } public Func Factory { get; set; } @@ -17,12 +17,14 @@ namespace Artemis.Core.Services internal abstract class WebApiControllerRegistration { - protected WebApiControllerRegistration(Type controllerType) + protected WebApiControllerRegistration(PluginFeature feature, Type controllerType) { + Feature = feature; ControllerType = controllerType; } public abstract object UntypedFactory { get; } public Type ControllerType { get; set; } + 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 new file mode 100644 index 000000000..fb7f03d13 --- /dev/null +++ b/src/Artemis.Core/Services/WebServer/WebModuleRegistration.cs @@ -0,0 +1,20 @@ +using System; +using EmbedIO; +using Ninject; + +namespace Artemis.Core.Services +{ + internal class WebModuleRegistration + { + public PluginFeature Feature { get; } + public Type WebModuleType { get; } + + public WebModuleRegistration(PluginFeature feature, Type webModuleType) + { + Feature = feature; + WebModuleType = webModuleType; + } + + public IWebModule CreateInstance() => (IWebModule) Feature.Plugin.Kernel!.Get(WebModuleType); + } +} \ 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 589004e29..a310fca63 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; using Artemis.Core.DataModelExpansions; @@ -8,7 +9,6 @@ using Artemis.Core.Modules; using EmbedIO; using EmbedIO.WebApi; using Newtonsoft.Json; -using Ninject; using Serilog; namespace Artemis.Core.Services @@ -16,18 +16,19 @@ namespace Artemis.Core.Services internal class WebServerService : IWebServerService, IDisposable { private readonly List _controllers; - private readonly IKernel _kernel; + private readonly List _modules; private readonly ILogger _logger; private readonly PluginSetting _webServerPortSetting; - public WebServerService(IKernel kernel, ILogger logger, ISettingsService settingsService) + public WebServerService(ILogger logger, ISettingsService settingsService, IPluginManagementService pluginManagementService) { - _kernel = kernel; _logger = logger; _controllers = new List(); + _modules = new List(); _webServerPortSetting = settingsService.GetSetting("WebServer.Port", 9696); _webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged; + pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled; PluginsModule = new PluginsModule("/plugins"); StartWebServer(); @@ -43,12 +44,18 @@ namespace Artemis.Core.Services Server?.Dispose(); Server = null; - WebApiModule apiModule = new("/api/", JsonNetSerializer); + WebApiModule apiModule = new("/", JsonNetSerializer); PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/"; WebServer server = new WebServer(o => o.WithUrlPrefix($"http://*:{_webServerPortSetting.Value}/").WithMode(HttpListenerMode.EmbedIO)) .WithLocalSessionManager() + .WithModule(PluginsModule); + + // Add registered modules + foreach (var webModule in _modules) + server = server.WithModule(webModule.CreateInstance()); + + server = server .WithModule(apiModule) - .WithModule(PluginsModule) .HandleHttpException((context, exception) => HandleHttpExceptionJson(context, exception)) .HandleUnhandledException(JsonExceptionHandlerCallback); @@ -166,9 +173,9 @@ namespace Artemis.Core.Services #region Controller management - public void AddController() where T : WebApiController + public void AddController(PluginFeature feature) where T : WebApiController { - _controllers.Add(new WebApiControllerRegistration(_kernel)); + _controllers.Add(new WebApiControllerRegistration(feature)); StartWebServer(); } @@ -180,6 +187,26 @@ namespace Artemis.Core.Services #endregion + #region Module management + + public void 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))); + StartWebServer(); + } + + public void RemoveModule() where T : IWebModule + { + _modules.RemoveAll(r => r.WebModuleType == typeof(T)); + StartWebServer(); + } + + #endregion + #region Handlers private async Task JsonExceptionHandlerCallback(IHttpContext context, Exception exception) @@ -235,6 +262,25 @@ namespace Artemis.Core.Services StartWebServer(); } + private void PluginManagementServiceOnPluginFeatureDisabled(object? sender, PluginFeatureEventArgs e) + { + bool mustRestart = false; + if (_controllers.Any(c => c.Feature == e.PluginFeature)) + { + mustRestart = true; + _controllers.RemoveAll(c => c.Feature == e.PluginFeature); + } + + if (_modules.Any(m => m.Feature == e.PluginFeature)) + { + mustRestart = true; + _modules.RemoveAll(m => m.Feature == e.PluginFeature); + } + + if (mustRestart) + StartWebServer(); + } + #endregion #region IDisposable diff --git a/src/Artemis.UI/ApplicationStateManager.cs b/src/Artemis.UI/ApplicationStateManager.cs index e2f1100a8..c751abcb6 100644 --- a/src/Artemis.UI/ApplicationStateManager.cs +++ b/src/Artemis.UI/ApplicationStateManager.cs @@ -64,7 +64,7 @@ namespace Artemis.UI string url = await File.ReadAllTextAsync(Path.Combine(Constants.DataFolder, "webserver.txt")); using HttpClient client = new(); - await client.PostAsync(url + "api/remote/bring-to-foreground", null!); + await client.PostAsync(url + "remote/bring-to-foreground", null!); } private void UtilitiesOnRestartRequested(object sender, RestartEventArgs e) diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index 9cbda99e6..f8c4f8733 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -106,7 +106,7 @@ namespace Artemis.UI.Services public void RegisterControllers() { - _webServerService.AddController(); + _webServerService.AddController(Constants.CorePlugin.Features.First().Instance!); } ///