1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

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
This commit is contained in:
Robert 2021-04-06 19:19:19 +02:00
parent e32b181d9f
commit ed704a165c
7 changed files with 102 additions and 20 deletions

View File

@ -55,7 +55,8 @@ namespace Artemis.Core.Services
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel; DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel;
/// <summary> /// <summary>
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="profileModule" />. /// Adds a new endpoint that directly maps received JSON to the data model of the provided
/// <paramref name="profileModule" />.
/// </summary> /// </summary>
/// <typeparam name="T">The data model type of the module</typeparam> /// <typeparam name="T">The data model type of the module</typeparam>
/// <param name="profileModule">The module whose datamodel to apply the received JSON to</param> /// <param name="profileModule">The module whose datamodel to apply the received JSON to</param>
@ -64,7 +65,8 @@ namespace Artemis.Core.Services
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(ProfileModule<T> profileModule, string endPointName) where T : DataModel; DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(ProfileModule<T> profileModule, string endPointName) where T : DataModel;
/// <summary> /// <summary>
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="dataModelExpansion" />. /// Adds a new endpoint that directly maps received JSON to the data model of the provided
/// <paramref name="dataModelExpansion" />.
/// </summary> /// </summary>
/// <typeparam name="T">The data model type of the module</typeparam> /// <typeparam name="T">The data model type of the module</typeparam>
/// <param name="dataModelExpansion">The data model expansion whose datamodel to apply the received JSON to</param> /// <param name="dataModelExpansion">The data model expansion whose datamodel to apply the received JSON to</param>
@ -114,7 +116,7 @@ namespace Artemis.Core.Services
/// Adds a new Web API controller and restarts the web server /// Adds a new Web API controller and restarts the web server
/// </summary> /// </summary>
/// <typeparam name="T">The type of Web API controller to remove</typeparam> /// <typeparam name="T">The type of Web API controller to remove</typeparam>
void AddController<T>() where T : WebApiController; void AddController<T>(PluginFeature feature) where T : WebApiController;
/// <summary> /// <summary>
/// Removes an existing Web API controller and restarts the web server /// Removes an existing Web API controller and restarts the web server
@ -122,6 +124,18 @@ namespace Artemis.Core.Services
/// <typeparam name="T">The type of Web API controller to remove</typeparam> /// <typeparam name="T">The type of Web API controller to remove</typeparam>
void RemoveController<T>() where T : WebApiController; void RemoveController<T>() where T : WebApiController;
/// <summary>
/// Adds a new EmbedIO module and restarts the web server
/// </summary>
/// <typeparam name="T">The type of module to add</typeparam>
void AddModule<T>(PluginFeature feature) where T : IWebModule;
/// <summary>
/// Removes a EmbedIO module and restarts the web server
/// </summary>
/// <typeparam name="T">The type of module to remove</typeparam>
void RemoveModule<T>() where T : IWebModule;
/// <summary> /// <summary>
/// Occurs when the web server has been created and is about to start. This is the ideal place to add your own modules. /// Occurs when the web server has been created and is about to start. This is the ideal place to add your own modules.
/// </summary> /// </summary>

View File

@ -48,13 +48,13 @@ namespace Artemis.Core.Services
protected override async Task OnRequestAsync(IHttpContext context) protected override async Task OnRequestAsync(IHttpContext context)
{ {
if (context.Route.SubPath == null) if (context.Route.SubPath == null)
throw HttpException.NotFound(); return;
// Split the sub path // Split the sub path
string[] pathParts = context.Route.SubPath.Substring(1).Split('/'); string[] pathParts = context.Route.SubPath.Substring(1).Split('/');
// Expect a plugin ID and an endpoint // Expect a plugin ID and an endpoint
if (pathParts == null || pathParts.Length != 2) if (pathParts.Length != 2)
throw HttpException.BadRequest("Path must contain a plugin ID and endpoint and nothing else."); return;
// Find a matching plugin // Find a matching plugin
if (!_pluginEndPoints.TryGetValue(pathParts[0], out Dictionary<string, PluginEndPoint>? endPoints)) if (!_pluginEndPoints.TryGetValue(pathParts[0], out Dictionary<string, PluginEndPoint>? endPoints))
@ -78,7 +78,7 @@ namespace Artemis.Core.Services
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool IsFinalHandler => true; public override bool IsFinalHandler => false;
internal string? ServerUrl { get; set; } internal string? ServerUrl { get; set; }

View File

@ -6,9 +6,9 @@ namespace Artemis.Core.Services
{ {
internal class WebApiControllerRegistration<T> : WebApiControllerRegistration where T : WebApiController internal class WebApiControllerRegistration<T> : WebApiControllerRegistration where T : WebApiController
{ {
public WebApiControllerRegistration(IKernel kernel) : base(typeof(T)) public WebApiControllerRegistration(PluginFeature feature) : base(feature, typeof(T))
{ {
Factory = () => kernel.Get<T>(); Factory = () => feature.Plugin.Kernel!.Get<T>();
} }
public Func<T> Factory { get; set; } public Func<T> Factory { get; set; }
@ -17,12 +17,14 @@ namespace Artemis.Core.Services
internal abstract class WebApiControllerRegistration internal abstract class WebApiControllerRegistration
{ {
protected WebApiControllerRegistration(Type controllerType) protected WebApiControllerRegistration(PluginFeature feature, Type controllerType)
{ {
Feature = feature;
ControllerType = controllerType; ControllerType = controllerType;
} }
public abstract object UntypedFactory { get; } public abstract object UntypedFactory { get; }
public Type ControllerType { get; set; } public Type ControllerType { get; set; }
public PluginFeature Feature { get; }
} }
} }

View File

@ -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);
}
}

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.DataModelExpansions; using Artemis.Core.DataModelExpansions;
@ -8,7 +9,6 @@ using Artemis.Core.Modules;
using EmbedIO; using EmbedIO;
using EmbedIO.WebApi; using EmbedIO.WebApi;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ninject;
using Serilog; using Serilog;
namespace Artemis.Core.Services namespace Artemis.Core.Services
@ -16,18 +16,19 @@ namespace Artemis.Core.Services
internal class WebServerService : IWebServerService, IDisposable internal class WebServerService : IWebServerService, IDisposable
{ {
private readonly List<WebApiControllerRegistration> _controllers; private readonly List<WebApiControllerRegistration> _controllers;
private readonly IKernel _kernel; private readonly List<WebModuleRegistration> _modules;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly PluginSetting<int> _webServerPortSetting; private readonly PluginSetting<int> _webServerPortSetting;
public WebServerService(IKernel kernel, ILogger logger, ISettingsService settingsService) public WebServerService(ILogger logger, ISettingsService settingsService, IPluginManagementService pluginManagementService)
{ {
_kernel = kernel;
_logger = logger; _logger = logger;
_controllers = new List<WebApiControllerRegistration>(); _controllers = new List<WebApiControllerRegistration>();
_modules = new List<WebModuleRegistration>();
_webServerPortSetting = settingsService.GetSetting("WebServer.Port", 9696); _webServerPortSetting = settingsService.GetSetting("WebServer.Port", 9696);
_webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged; _webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged;
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
PluginsModule = new PluginsModule("/plugins"); PluginsModule = new PluginsModule("/plugins");
StartWebServer(); StartWebServer();
@ -43,12 +44,18 @@ namespace Artemis.Core.Services
Server?.Dispose(); Server?.Dispose();
Server = null; Server = null;
WebApiModule apiModule = new("/api/", JsonNetSerializer); WebApiModule apiModule = new("/", JsonNetSerializer);
PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/"; PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/";
WebServer server = new WebServer(o => o.WithUrlPrefix($"http://*:{_webServerPortSetting.Value}/").WithMode(HttpListenerMode.EmbedIO)) WebServer server = new WebServer(o => o.WithUrlPrefix($"http://*:{_webServerPortSetting.Value}/").WithMode(HttpListenerMode.EmbedIO))
.WithLocalSessionManager() .WithLocalSessionManager()
.WithModule(PluginsModule);
// Add registered modules
foreach (var webModule in _modules)
server = server.WithModule(webModule.CreateInstance());
server = server
.WithModule(apiModule) .WithModule(apiModule)
.WithModule(PluginsModule)
.HandleHttpException((context, exception) => HandleHttpExceptionJson(context, exception)) .HandleHttpException((context, exception) => HandleHttpExceptionJson(context, exception))
.HandleUnhandledException(JsonExceptionHandlerCallback); .HandleUnhandledException(JsonExceptionHandlerCallback);
@ -166,9 +173,9 @@ namespace Artemis.Core.Services
#region Controller management #region Controller management
public void AddController<T>() where T : WebApiController public void AddController<T>(PluginFeature feature) where T : WebApiController
{ {
_controllers.Add(new WebApiControllerRegistration<T>(_kernel)); _controllers.Add(new WebApiControllerRegistration<T>(feature));
StartWebServer(); StartWebServer();
} }
@ -180,6 +187,26 @@ namespace Artemis.Core.Services
#endregion #endregion
#region Module management
public void AddModule<T>(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<T>() where T : IWebModule
{
_modules.RemoveAll(r => r.WebModuleType == typeof(T));
StartWebServer();
}
#endregion
#region Handlers #region Handlers
private async Task JsonExceptionHandlerCallback(IHttpContext context, Exception exception) private async Task JsonExceptionHandlerCallback(IHttpContext context, Exception exception)
@ -235,6 +262,25 @@ namespace Artemis.Core.Services
StartWebServer(); 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 #endregion
#region IDisposable #region IDisposable

View File

@ -64,7 +64,7 @@ namespace Artemis.UI
string url = await File.ReadAllTextAsync(Path.Combine(Constants.DataFolder, "webserver.txt")); string url = await File.ReadAllTextAsync(Path.Combine(Constants.DataFolder, "webserver.txt"));
using HttpClient client = new(); 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) private void UtilitiesOnRestartRequested(object sender, RestartEventArgs e)

View File

@ -106,7 +106,7 @@ namespace Artemis.UI.Services
public void RegisterControllers() public void RegisterControllers()
{ {
_webServerService.AddController<RemoteController>(); _webServerService.AddController<RemoteController>(Constants.CorePlugin.Features.First().Instance!);
} }
/// <inheritdoc /> /// <inheritdoc />