mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Web server - Added serveral plugin end point types
This commit is contained in:
parent
d6ba573456
commit
fe847ad8f4
@ -65,6 +65,7 @@
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cstorage/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cstorage_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cendpoints/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores_005Cregistrations/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using EmbedIO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a plugin web endpoint receiving an object of type <typeparamref name="T" /> and returning any
|
||||
/// <see cref="object" /> or <see langword="null" />.
|
||||
/// <para>Note: Both will be deserialized and serialized respectively using JSON.</para>
|
||||
/// </summary>
|
||||
public class JsonPluginEndPoint<T> : PluginEndPoint
|
||||
{
|
||||
private readonly Action<T>? _requestHandler;
|
||||
private readonly Func<T, object?>? _responseRequestHandler;
|
||||
|
||||
internal JsonPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Action<T> requestHandler) : base(pluginFeature, name, pluginsModule)
|
||||
{
|
||||
_requestHandler = requestHandler;
|
||||
ThrowOnFail = true;
|
||||
}
|
||||
|
||||
internal JsonPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Func<T, object?> responseRequestHandler) : base(pluginFeature, name, pluginsModule)
|
||||
{
|
||||
_responseRequestHandler = responseRequestHandler;
|
||||
ThrowOnFail = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the end point should throw an exception if deserializing the received JSON fails.
|
||||
/// If set to <see langword="false" /> malformed JSON is silently ignored; if set to <see langword="true" /> malformed
|
||||
/// JSON throws a <see cref="JsonException" />.
|
||||
/// </summary>
|
||||
public bool ThrowOnFail { get; set; }
|
||||
|
||||
#region Overrides of PluginEndPoint
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override void ProcessRequest(IHttpContext context)
|
||||
{
|
||||
if (context.Request.HttpVerb != HttpVerbs.Post)
|
||||
throw HttpException.MethodNotAllowed("This end point only accepts POST calls");
|
||||
|
||||
context.Response.ContentType = MimeType.Json;
|
||||
|
||||
using TextReader reader = context.OpenRequestText();
|
||||
object? response = null;
|
||||
try
|
||||
{
|
||||
T deserialized = JsonConvert.DeserializeObject<T>(reader.ReadToEnd());
|
||||
|
||||
if (_requestHandler != null)
|
||||
{
|
||||
_requestHandler(deserialized);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_responseRequestHandler != null)
|
||||
response = _responseRequestHandler(deserialized);
|
||||
else
|
||||
throw new ArtemisCoreException("JSON plugin end point has no request handler");
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
if (ThrowOnFail)
|
||||
throw;
|
||||
}
|
||||
|
||||
using TextWriter writer = context.OpenResponseText();
|
||||
writer.Write(JsonConvert.SerializeObject(response));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ using EmbedIO;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
public class PluginEndPoint
|
||||
public abstract class PluginEndPoint
|
||||
{
|
||||
private readonly PluginsModule _pluginsModule;
|
||||
|
||||
@ -22,19 +22,16 @@ namespace Artemis.Core.Services
|
||||
public PluginFeature PluginFeature { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the end point
|
||||
/// Gets the name of the end point
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full URL of the end point
|
||||
/// 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();
|
||||
}
|
||||
internal abstract void ProcessRequest(IHttpContext context);
|
||||
|
||||
private void OnDisabled(object? sender, EventArgs e)
|
||||
{
|
||||
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using EmbedIO;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a plugin web endpoint that handles a raw <see cref="IHttpContext" />.
|
||||
/// <para>
|
||||
/// Note: This requires that you reference the EmbedIO
|
||||
/// <see href="https://www.nuget.org/packages/embedio">Nuget package</see>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class RawPluginEndPoint : PluginEndPoint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal RawPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Action<IHttpContext> requestHandler) : base(pluginFeature, name, pluginsModule)
|
||||
{
|
||||
RequestHandler = requestHandler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler used to handle incoming requests to this endpoint
|
||||
/// </summary>
|
||||
public Action<IHttpContext> RequestHandler { get; }
|
||||
|
||||
#region Overrides of PluginEndPoint
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override void ProcessRequest(IHttpContext context)
|
||||
{
|
||||
RequestHandler(context);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using EmbedIO;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a plugin web endpoint receiving an a <see cref="string" /> and returning a <see cref="string" /> or
|
||||
/// <see langword="null" />.
|
||||
/// </summary>
|
||||
public class StringPluginEndPoint : PluginEndPoint
|
||||
{
|
||||
private readonly Action<string>? _requestHandler;
|
||||
private readonly Func<string, string?>? _responseRequestHandler;
|
||||
|
||||
internal StringPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Action<string> requestHandler) : base(pluginFeature, name, pluginsModule)
|
||||
{
|
||||
_requestHandler = requestHandler;
|
||||
}
|
||||
|
||||
internal StringPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Func<string, string?> requestHandler) : base(pluginFeature, name, pluginsModule)
|
||||
{
|
||||
_responseRequestHandler = requestHandler;
|
||||
}
|
||||
|
||||
#region Overrides of PluginEndPoint
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override void ProcessRequest(IHttpContext context)
|
||||
{
|
||||
if (context.Request.HttpVerb != HttpVerbs.Post)
|
||||
throw HttpException.MethodNotAllowed("This end point only accepts POST calls");
|
||||
|
||||
context.Response.ContentType = MimeType.PlainText;
|
||||
|
||||
using TextReader reader = context.OpenRequestText();
|
||||
string? response;
|
||||
if (_requestHandler != null)
|
||||
{
|
||||
_requestHandler(reader.ReadToEnd());
|
||||
return;
|
||||
}
|
||||
|
||||
else if (_responseRequestHandler != null)
|
||||
response = _responseRequestHandler(reader.ReadToEnd());
|
||||
else
|
||||
throw new ArtemisCoreException("String plugin end point has no request handler");
|
||||
|
||||
using TextWriter writer = context.OpenResponseText();
|
||||
writer.Write(response);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -15,12 +15,59 @@ namespace Artemis.Core.Services
|
||||
WebServer? Server { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new endpoint for the given plugin feature
|
||||
/// Adds a new endpoint for the given plugin feature receiving an object of type <typeparamref name="T" />
|
||||
/// <para>Note: Object will be deserialized using JSON.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to be received</typeparam>
|
||||
/// <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>
|
||||
/// <param name="requestHandler"></param>
|
||||
/// <returns>The resulting end point</returns>
|
||||
JsonPluginEndPoint<T> AddJsonEndPoint<T>(PluginFeature feature, string endPointName, Action<T> requestHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new endpoint for the given plugin feature receiving an object of type <typeparamref name="T" /> and
|
||||
/// returning any <see cref="object" />.
|
||||
/// <para>Note: Both will be deserialized and serialized respectively using JSON.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to be received</typeparam>
|
||||
/// <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>
|
||||
/// <param name="requestHandler"></param>
|
||||
/// <returns>The resulting end point</returns>
|
||||
JsonPluginEndPoint<T> AddResponsiveJsonEndPoint<T>(PluginFeature feature, string endPointName, Func<T, object?> requestHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
|
||||
/// </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>
|
||||
/// <param name="requestHandler"></param>
|
||||
/// <returns>The resulting end point</returns>
|
||||
PluginEndPoint AddPluginEndPoint(PluginFeature feature, string endPointName);
|
||||
StringPluginEndPoint AddStringEndPoint(PluginFeature feature, string endPointName, Action<string> requestHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" /> and returning a
|
||||
/// <see cref="string" /> or <see langword="null" />.
|
||||
/// </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>
|
||||
/// <param name="requestHandler"></param>
|
||||
/// <returns>The resulting end point</returns>
|
||||
StringPluginEndPoint AddResponsiveStringEndPoint(PluginFeature feature, string endPointName, Func<string, string?> requestHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new endpoint for the given plugin feature that handles a raw <see cref="IHttpContext" />.
|
||||
/// <para>
|
||||
/// Note: This requires that you reference the EmbedIO
|
||||
/// <see href="https://www.nuget.org/packages/embedio">Nuget package</see>.
|
||||
/// </para>
|
||||
/// </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>
|
||||
/// <param name="requestHandler"></param>
|
||||
/// <returns>The resulting end point</returns>
|
||||
RawPluginEndPoint AddRawEndPoint(PluginFeature feature, string endPointName, Action<IHttpContext> requestHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an existing endpoint
|
||||
|
||||
@ -56,26 +56,23 @@ 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);
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@ using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO;
|
||||
using EmbedIO.Actions;
|
||||
using EmbedIO.WebApi;
|
||||
using Newtonsoft.Json;
|
||||
using Ninject;
|
||||
@ -14,11 +13,11 @@ namespace Artemis.Core.Services
|
||||
{
|
||||
internal class WebServerService : IWebServerService, IDisposable
|
||||
{
|
||||
private readonly List<WebApiControllerRegistration> _controllers;
|
||||
private readonly IKernel _kernel;
|
||||
private readonly ILogger _logger;
|
||||
private readonly PluginsModule _pluginModule;
|
||||
private readonly PluginSetting<int> _webServerPortSetting;
|
||||
private readonly List<WebApiControllerRegistration> _controllers;
|
||||
|
||||
public WebServerService(IKernel kernel, ILogger logger, ISettingsService settingsService)
|
||||
{
|
||||
@ -34,6 +33,27 @@ namespace Artemis.Core.Services
|
||||
Server.Start();
|
||||
}
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void WebServerPortSettingOnSettingChanged(object? sender, EventArgs e)
|
||||
{
|
||||
Server = CreateWebServer();
|
||||
Server.Start();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Server?.Dispose();
|
||||
_webServerPortSetting.SettingChanged -= WebServerPortSettingOnSettingChanged;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public WebServer? Server { get; private set; }
|
||||
|
||||
#region Web server managament
|
||||
@ -72,32 +92,54 @@ namespace Artemis.Core.Services
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void WebServerPortSettingOnSettingChanged(object? sender, EventArgs e)
|
||||
{
|
||||
Server = CreateWebServer();
|
||||
Server.Start();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Server?.Dispose();
|
||||
_webServerPortSetting.SettingChanged -= WebServerPortSettingOnSettingChanged;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Plugin endpoint management
|
||||
|
||||
public PluginEndPoint AddPluginEndPoint(PluginFeature feature, string endPointName)
|
||||
public JsonPluginEndPoint<T> AddJsonEndPoint<T>(PluginFeature feature, string endPointName, Action<T> requestHandler)
|
||||
{
|
||||
PluginEndPoint endPoint = new(feature, endPointName, _pluginModule);
|
||||
if (feature == null) throw new ArgumentNullException(nameof(feature));
|
||||
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
|
||||
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
|
||||
JsonPluginEndPoint<T> endPoint = new(feature, endPointName, _pluginModule, requestHandler);
|
||||
_pluginModule.AddPluginEndPoint(endPoint);
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
public JsonPluginEndPoint<T> AddResponsiveJsonEndPoint<T>(PluginFeature feature, string endPointName, Func<T, object?> requestHandler)
|
||||
{
|
||||
if (feature == null) throw new ArgumentNullException(nameof(feature));
|
||||
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
|
||||
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
|
||||
JsonPluginEndPoint<T> endPoint = new(feature, endPointName, _pluginModule, requestHandler);
|
||||
_pluginModule.AddPluginEndPoint(endPoint);
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
public StringPluginEndPoint AddStringEndPoint(PluginFeature feature, string endPointName, Action<string> requestHandler)
|
||||
{
|
||||
if (feature == null) throw new ArgumentNullException(nameof(feature));
|
||||
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
|
||||
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
|
||||
StringPluginEndPoint endPoint = new(feature, endPointName, _pluginModule, requestHandler);
|
||||
_pluginModule.AddPluginEndPoint(endPoint);
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
public StringPluginEndPoint AddResponsiveStringEndPoint(PluginFeature feature, string endPointName, Func<string, string?> requestHandler)
|
||||
{
|
||||
if (feature == null) throw new ArgumentNullException(nameof(feature));
|
||||
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
|
||||
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
|
||||
StringPluginEndPoint endPoint = new(feature, endPointName, _pluginModule, requestHandler);
|
||||
_pluginModule.AddPluginEndPoint(endPoint);
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
public RawPluginEndPoint AddRawEndPoint(PluginFeature feature, string endPointName, Action<IHttpContext> requestHandler)
|
||||
{
|
||||
if (feature == null) throw new ArgumentNullException(nameof(feature));
|
||||
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
|
||||
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
|
||||
RawPluginEndPoint endPoint = new(feature, endPointName, _pluginModule, requestHandler);
|
||||
_pluginModule.AddPluginEndPoint(endPoint);
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user