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

Web server - Plugin end point API WIP

This commit is contained in:
Robert 2021-01-28 19:54:04 +01:00
parent 5c2a96eee0
commit d6ba573456
4 changed files with 151 additions and 2 deletions

View File

@ -14,6 +14,20 @@ namespace Artemis.Core.Services
/// </summary>
WebServer? Server { get; }
/// <summary>
/// Adds a new endpoint for the given plugin feature
/// </summary>
/// <param name="feature">The plugin feature the end point is associated with</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <returns>The resulting end point</returns>
PluginEndPoint AddPluginEndPoint(PluginFeature feature, string endPointName);
/// <summary>
/// Removes an existing endpoint
/// </summary>
/// <param name="endPoint">The end point to remove</param>
void RemovePluginEndPoint(PluginEndPoint endPoint);
/// <summary>
/// Adds a new Web API controller and restarts the web server
/// </summary>

View File

@ -0,0 +1,45 @@
using System;
using EmbedIO;
namespace Artemis.Core.Services
{
public class PluginEndPoint
{
private readonly PluginsModule _pluginsModule;
internal PluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule)
{
_pluginsModule = pluginsModule;
PluginFeature = pluginFeature;
Name = name;
PluginFeature.Disabled += OnDisabled;
}
/// <summary>
/// Gets the plugin the data model is associated with
/// </summary>
public PluginFeature PluginFeature { get; }
/// <summary>
/// Gets the name of the end point
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the full URL of the end point
/// </summary>
public string Url => $"{_pluginsModule.BaseRoute}{PluginFeature.Plugin.Guid}/{Name}";
internal void ProcessRequest(IHttpContext context)
{
throw new NotImplementedException();
}
private void OnDisabled(object? sender, EventArgs e)
{
PluginFeature.Disabled -= OnDisabled;
_pluginsModule.RemovePluginEndPoint(this);
}
}
}

View File

@ -1,13 +1,54 @@
using System.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using EmbedIO;
using Newtonsoft.Json;
namespace Artemis.Core.Services
{
internal class PluginsModule : WebModuleBase
{
private readonly Dictionary<string, Dictionary<string, PluginEndPoint>> _pluginEndPoints;
/// <inheritdoc />
public PluginsModule(string baseRoute) : base(baseRoute)
{
_pluginEndPoints = new Dictionary<string, Dictionary<string, PluginEndPoint>>();
OnUnhandledException += HandleUnhandledExceptionJson;
}
public void AddPluginEndPoint(PluginEndPoint registration)
{
string id = registration.PluginFeature.Plugin.Guid.ToString();
if (!_pluginEndPoints.TryGetValue(id, out Dictionary<string, PluginEndPoint>? registrations))
{
registrations = new Dictionary<string, PluginEndPoint>();
_pluginEndPoints.Add(id, registrations);
}
if (registrations.ContainsKey(registration.Name))
throw new ArtemisPluginException(registration.PluginFeature.Plugin, $"Plugin already registered an endpoint at {registration.Name}.");
registrations.Add(registration.Name, registration);
}
public void RemovePluginEndPoint(PluginEndPoint registration)
{
string id = registration.PluginFeature.Plugin.Guid.ToString();
if (!_pluginEndPoints.TryGetValue(id, out Dictionary<string, PluginEndPoint>? registrations))
return;
if (!registrations.ContainsKey(registration.Name))
return;
registrations.Remove(registration.Name);
}
private async Task HandleUnhandledExceptionJson(IHttpContext context, Exception exception)
{
await context.SendStringAsync(
JsonConvert.SerializeObject(new ArtemisPluginException("The plugin failed to process the request", exception), Formatting.Indented),
MimeType.Json,
Encoding.UTF8
);
}
#region Overrides of WebModuleBase
@ -15,6 +56,31 @@ namespace Artemis.Core.Services
/// <inheritdoc />
protected override async Task OnRequestAsync(IHttpContext context)
{
// Always stick to JSON
context.Response.ContentType = MimeType.Json;
if (context.Route.SubPath == null)
throw HttpException.NotFound();
// 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.");
// Find a matching plugin
if (!_pluginEndPoints.TryGetValue(pathParts[0], out Dictionary<string, PluginEndPoint>? endPoints))
throw HttpException.NotFound($"Found no plugin with ID {pathParts[0]}.");
// Find a matching endpoint
if (!endPoints.TryGetValue(pathParts[1], out PluginEndPoint? endPoint))
throw HttpException.NotFound($"Found no endpoint called {pathParts[1]} for plugin with ID {pathParts[0]}.");
// It is up to the registration how the request is eventually handled, it might even set a response here
endPoint.ProcessRequest(context);
// No need to return ourselves, assume the request is fully handled by the end point
context.SetHandled();
}
/// <inheritdoc />

View File

@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using EmbedIO;
using EmbedIO.Actions;
using EmbedIO.WebApi;
using Newtonsoft.Json;
using Ninject;
using Serilog;
@ -46,7 +49,7 @@ namespace Artemis.Core.Services
.WithLocalSessionManager()
.WithModule(apiModule)
.WithModule(_pluginModule)
.WithModule(new ActionModule("/", HttpVerbs.Any, ctx => ctx.SendDataAsync(new {Message = "Error"})));
.HandleHttpException((context, exception) => HandleHttpExceptionJson(context, exception));
// Add controllers to the API module
foreach (WebApiControllerRegistration registration in _controllers)
@ -62,6 +65,11 @@ namespace Artemis.Core.Services
return server;
}
private async Task HandleHttpExceptionJson(IHttpContext context, IHttpException httpException)
{
await context.SendStringAsync(JsonConvert.SerializeObject(httpException, Formatting.Indented), MimeType.Json, Encoding.UTF8);
}
#endregion
#region Event handlers
@ -85,6 +93,22 @@ namespace Artemis.Core.Services
#endregion
#region Plugin endpoint management
public PluginEndPoint AddPluginEndPoint(PluginFeature feature, string endPointName)
{
PluginEndPoint endPoint = new(feature, endPointName, _pluginModule);
_pluginModule.AddPluginEndPoint(endPoint);
return endPoint;
}
public void RemovePluginEndPoint(PluginEndPoint endPoint)
{
_pluginModule.RemovePluginEndPoint(endPoint);
}
#endregion
#region Controller management
public void AddController<T>() where T : WebApiController