aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/Net.Http/src/Core/ConnectionInfo.cs34
-rw-r--r--lib/Net.Http/src/Core/HttpContext.cs11
-rw-r--r--lib/Net.Http/src/Core/HttpServerBase.cs14
-rw-r--r--lib/Net.Http/src/Core/HttpServerProcessing.cs72
-rw-r--r--lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs4
-rw-r--r--lib/Net.Http/src/Helpers/TransportReader.cs23
-rw-r--r--lib/Net.Http/src/IHttpServer.cs52
-rw-r--r--lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs4
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs6
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs126
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/PluginLoadConfiguration.cs2
-rw-r--r--lib/Plugins.Essentials/src/Accounts/AccountUtils.cs5
-rw-r--r--lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs43
-rw-r--r--lib/Plugins.Essentials/src/Endpoints/VirtualEndpoint.cs5
-rw-r--r--lib/Plugins.Essentials/src/EventProcessor.cs14
-rw-r--r--lib/Plugins.Runtime/src/PluginController.cs2
-rw-r--r--lib/Plugins.Runtime/src/RuntimePluginLoader.cs25
-rw-r--r--lib/Utils/README.md5
18 files changed, 314 insertions, 133 deletions
diff --git a/lib/Net.Http/src/Core/ConnectionInfo.cs b/lib/Net.Http/src/Core/ConnectionInfo.cs
index d193467..e680152 100644
--- a/lib/Net.Http/src/Core/ConnectionInfo.cs
+++ b/lib/Net.Http/src/Core/ConnectionInfo.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -87,6 +87,7 @@ namespace VNLib.Net.Http
string contentType = HttpHelpers.GetContentTypeString(type);
return Accepts(contentType);
}
+
///<inheritdoc/>
public bool Accepts(string contentType)
{
@@ -96,22 +97,29 @@ namespace VNLib.Net.Http
}
//If client accepts exact requested encoding
- if (Accept.Contains(contentType))
+ if (Accept.Contains(contentType, StringComparer.OrdinalIgnoreCase))
{
return true;
}
-
- //Search accept types to determine if the content type is acceptable
- bool accepted = Accept
- .Where(ctype =>
+
+ //Search for the content-sub-type
+
+ //Get prinary side of mime type
+ ReadOnlySpan<char> primary = contentType.AsSpan().SliceBeforeParam('/');
+
+ for (int i = 0; i < Context.Request.Accept.Count; i++)
+ {
+ //The the accept subtype
+ ReadOnlySpan<char> ctSubType = Context.Request.Accept[i].AsSpan().SliceBeforeParam('/');
+
+ //See if accepts any subtype, or the primary sub-type matches
+ if(ctSubType[0] == '*' || ctSubType.Equals(primary, StringComparison.OrdinalIgnoreCase))
{
- //Get prinary side of mime type
- ReadOnlySpan<char> primary = contentType.AsSpan().SliceBeforeParam('/');
- ReadOnlySpan<char> ctSubType = ctype.AsSpan().SliceBeforeParam('/');
- //See if accepts any subtype, or the primary sub-type matches
- return ctSubType[0] == '*' || ctSubType.Equals(primary, StringComparison.OrdinalIgnoreCase);
- }).Any();
- return accepted;
+ return true;
+ }
+ }
+
+ return false;
}
/// <summary>
diff --git a/lib/Net.Http/src/Core/HttpContext.cs b/lib/Net.Http/src/Core/HttpContext.cs
index 43d1975..7e34dff 100644
--- a/lib/Net.Http/src/Core/HttpContext.cs
+++ b/lib/Net.Http/src/Core/HttpContext.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -71,6 +71,11 @@ namespace VNLib.Net.Http.Core
/// <summary>
/// Gets or sets the alternate application protocol to swtich to
/// </summary>
+ /// <remarks>
+ /// This property is only cleared when the context is released for reuse
+ /// so when this property contains a value, the context must be released
+ /// or this property must be exlicitly cleared
+ /// </remarks>
public IAlternateProtocol? AlternateProtocol { get; set; }
private readonly ResponseWriter responseWriter;
@@ -138,8 +143,6 @@ namespace VNLib.Net.Http.Core
///<inheritdoc/>
public void EndRequest()
{
- AlternateProtocol = null;
-
Request.OnComplete();
Response.OnComplete();
RequestBuffer.OnComplete();
@@ -157,6 +160,8 @@ namespace VNLib.Net.Http.Core
{
_ctx = null;
+ AlternateProtocol = null;
+
//Release response/requqests
Request.OnRelease();
Response.OnRelease();
diff --git a/lib/Net.Http/src/Core/HttpServerBase.cs b/lib/Net.Http/src/Core/HttpServerBase.cs
index 3daa3cc..3dac217 100644
--- a/lib/Net.Http/src/Core/HttpServerBase.cs
+++ b/lib/Net.Http/src/Core/HttpServerBase.cs
@@ -56,7 +56,7 @@ namespace VNLib.Net.Http
/// with extensable processors and transport providers.
/// This class cannot be inherited
/// </summary>
- public sealed partial class HttpServer : ICacheHolder
+ public sealed partial class HttpServer : ICacheHolder, IHttpServer
{
/// <summary>
/// The host key that determines a "wildcard" host, meaning the
@@ -206,19 +206,19 @@ namespace VNLib.Net.Http
/// <summary>
/// Begins listening for connections on configured interfaces for configured hostnames.
/// </summary>
- /// <param name="token">A token used to stop listening for incomming connections and close all open websockets</param>
+ /// <param name="cancellationToken">A token used to stop listening for incomming connections and close all open websockets</param>
/// <returns>A task that resolves when the server has exited</returns>
/// <exception cref="SocketException"></exception>
/// <exception cref="ThreadStateException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
/// <exception cref="InvalidOperationException"></exception>
- public Task Start(CancellationToken token)
+ public Task Start(CancellationToken cancellationToken)
{
- StopToken = CancellationTokenSource.CreateLinkedTokenSource(token);
+ StopToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
//Start servers with the new token source
Transport.Start(StopToken.Token);
//Start the listen task
- return Task.Run(ListenWorkerDoWork, token);
+ return Task.Run(ListenWorkerDoWork, cancellationToken);
}
/*
@@ -244,6 +244,7 @@ namespace VNLib.Net.Http
Running = true;
Config.ServerLog.Information("HTTP server {hc} listening for connections", GetHashCode());
+
//Listen for connections until canceled
while (true)
{
@@ -251,6 +252,7 @@ namespace VNLib.Net.Http
{
//Listen for new connection
ITransportContext ctx = await Transport.AcceptAsync(StopToken!.Token);
+
//Try to dispatch the recieved event
_ = DataReceivedAsync(ctx).ConfigureAwait(false);
}
@@ -268,8 +270,10 @@ namespace VNLib.Net.Http
Config.ServerLog.Error(ex);
}
}
+
//Clear all caches
CacheHardClear();
+
//Clear running flag
Running = false;
Config.ServerLog.Information("HTTP server {hc} exiting", GetHashCode());
diff --git a/lib/Net.Http/src/Core/HttpServerProcessing.cs b/lib/Net.Http/src/Core/HttpServerProcessing.cs
index 881b66c..886f735 100644
--- a/lib/Net.Http/src/Core/HttpServerProcessing.cs
+++ b/lib/Net.Http/src/Core/HttpServerProcessing.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -30,7 +30,6 @@ using System.Net.Sockets;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
-using VNLib.Utils;
using VNLib.Utils.Logging;
using VNLib.Net.Http.Core;
@@ -48,8 +47,8 @@ namespace VNLib.Net.Http
Interlocked.Increment(ref OpenConnectionCount);
//Rent a new context object to reuse
- HttpContext context = ContextStore.Rent();
-
+ HttpContext? context = ContextStore.Rent();
+
try
{
//Set write timeout
@@ -65,14 +64,14 @@ namespace VNLib.Net.Http
transportContext.ConnectionStream.ReadTimeout = Config.ActiveConnectionRecvTimeout;
//Process the request
- ERRNO keepalive = await ProcessHttpEventAsync(transportContext, context);
-
- //If the connection is closed, we can return
- if (!keepalive)
+ bool keepAlive = await ProcessHttpEventAsync(transportContext, context);
+
+ //If not keepalive, exit the listening loop
+ if (!keepAlive)
{
break;
}
-
+
//Set inactive keeaplive timeout
transportContext.ConnectionStream.ReadTimeout = (int)Config.ConnectionKeepAlive.TotalMilliseconds;
@@ -80,6 +79,24 @@ namespace VNLib.Net.Http
await transportContext.ConnectionStream.ReadAsync(Memory<byte>.Empty, StopToken!.Token);
} while (true);
+
+ //Check if an alternate protocol was specified
+ if (context.AlternateProtocol != null)
+ {
+ //Save the current ap
+ IAlternateProtocol ap = context.AlternateProtocol;
+
+ //Release the context before listening to free it back to the pool
+ ContextStore.Return(context);
+ context = null;
+
+ //Remove transport timeouts
+ transportContext.ConnectionStream.ReadTimeout = Timeout.Infinite;
+ transportContext.ConnectionStream.WriteTimeout = Timeout.Infinite;
+
+ //Listen on the alternate protocol
+ await ap.RunAsync(transportContext.ConnectionStream, StopToken!.Token).ConfigureAwait(false);
+ }
}
//Catch wrapped socket exceptions
catch(IOException ioe) when(ioe.InnerException is SocketException se)
@@ -90,7 +107,7 @@ namespace VNLib.Net.Http
{
WriteSocketExecption(se);
}
- catch (OperationCanceledException oce)
+ catch(OperationCanceledException oce)
{
Config.ServerLog.Debug("Failed to receive transport data within a timeout period {m}, connection closed", oce.Message);
}
@@ -102,8 +119,12 @@ namespace VNLib.Net.Http
//Dec open connection count
Interlocked.Decrement(ref OpenConnectionCount);
- //Return context to store
- ContextStore.Return(context);
+ //Return the context for normal operation
+ if(context != null)
+ {
+ //Return context to store
+ ContextStore.Return(context);
+ }
//Close the transport async
try
@@ -123,7 +144,7 @@ namespace VNLib.Net.Http
/// <param name="transportContext">The <see cref="ITransportContext"/> describing the incoming connection</param>
/// <param name="context">Reusable context object</param>
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
- private async Task<ERRNO> ProcessHttpEventAsync(ITransportContext transportContext, HttpContext context)
+ private async Task<bool> ProcessHttpEventAsync(ITransportContext transportContext, HttpContext context)
{
//Prepare http context to process a new message
context.BeginRequest();
@@ -146,25 +167,16 @@ namespace VNLib.Net.Http
}
#endif
//process the request
- ERRNO keepalive = await ProcessRequestAsync(context, (HttpStatusCode)status);
- //Store alternate protocol if set
- IAlternateProtocol? alternateProtocol = context.AlternateProtocol;
+ bool keepalive = await ProcessRequestAsync(context, (HttpStatusCode)status);
+
//Close the response
await context.WriteResponseAsync(StopToken!.Token);
- //See if an alterate protocol was specified
- if (alternateProtocol != null)
- {
- //Disable transport timeouts
- transportContext.ConnectionStream.WriteTimeout = Timeout.Infinite;
- transportContext.ConnectionStream.ReadTimeout = Timeout.Infinite;
- //Exec the protocol handler and pass the transport stream
- await alternateProtocol.RunAsync(transportContext.ConnectionStream, StopToken!.Token);
-
- //Clear keepalive flag to close the connection
- keepalive = false;
- }
+
+ /*
+ * If an alternate protocol was specified, we need to break the keepalive loop
+ */
- return keepalive;
+ return keepalive & context.AlternateProtocol == null;
}
finally
{
@@ -236,7 +248,7 @@ namespace VNLib.Net.Http
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
- private async ValueTask<ERRNO> ProcessRequestAsync(HttpContext context, HttpStatusCode status)
+ private async ValueTask<bool> ProcessRequestAsync(HttpContext context, HttpStatusCode status)
{
//Check status
if (status != 0)
diff --git a/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs b/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
index 6a93192..3841034 100644
--- a/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
+++ b/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -114,7 +114,7 @@ namespace VNLib.Net.Http.Core
/// Initializes the <see cref="HttpRequest"/> for an incomming connection
/// </summary>
/// <param name="server"></param>
- /// <param name="ctx">The <see cref="TransportEventContext"/> to attach the request to</param>
+ /// <param name="ctx">The <see cref="ITransportContext"/> to attach the request to</param>
/// <param name="defaultHttpVersion">The default http version</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Initialize(this HttpRequest server, ITransportContext ctx, HttpVersion defaultHttpVersion)
diff --git a/lib/Net.Http/src/Helpers/TransportReader.cs b/lib/Net.Http/src/Helpers/TransportReader.cs
index a37bfe9..722120b 100644
--- a/lib/Net.Http/src/Helpers/TransportReader.cs
+++ b/lib/Net.Http/src/Helpers/TransportReader.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -29,7 +29,6 @@ using System.Text;
using VNLib.Utils;
using VNLib.Utils.IO;
-
namespace VNLib.Net.Http.Core
{
@@ -39,18 +38,14 @@ namespace VNLib.Net.Http.Core
internal struct TransportReader : IVnTextReader
{
///<inheritdoc/>
- public readonly Encoding Encoding => _encoding;
+ public readonly Encoding Encoding { get; }
///<inheritdoc/>
- public readonly ReadOnlyMemory<byte> LineTermination => _lineTermination;
+ public readonly ReadOnlyMemory<byte> LineTermination { get; }
///<inheritdoc/>
- public readonly Stream BaseStream => _transport;
-
+ public readonly Stream BaseStream { get; }
private readonly SharedHeaderReaderBuffer BinBuffer;
- private readonly Encoding _encoding;
- private readonly Stream _transport;
- private readonly ReadOnlyMemory<byte> _lineTermination;
-
+
private int BufWindowStart;
private int BufWindowEnd;
@@ -65,9 +60,9 @@ namespace VNLib.Net.Http.Core
{
BufWindowEnd = 0;
BufWindowStart = 0;
- _encoding = encoding;
- _transport = transport;
- _lineTermination = lineTermination;
+ Encoding = encoding;
+ BaseStream = transport;
+ LineTermination = lineTermination;
BinBuffer = buffer;
}
@@ -86,7 +81,7 @@ namespace VNLib.Net.Http.Core
//Get a buffer from the end of the current window to the end of the buffer
Span<byte> bufferWindow = BinBuffer.BinBuffer[BufWindowEnd..];
//Read from stream
- int read = _transport.Read(bufferWindow);
+ int read = BaseStream.Read(bufferWindow);
//Update the end of the buffer window to the end of the read data
BufWindowEnd += read;
}
diff --git a/lib/Net.Http/src/IHttpServer.cs b/lib/Net.Http/src/IHttpServer.cs
new file mode 100644
index 0000000..ccee8fd
--- /dev/null
+++ b/lib/Net.Http/src/IHttpServer.cs
@@ -0,0 +1,52 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHttpServer.cs
+*
+* IHttpServer.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Provides an HTTP based application layer protocol server
+ /// </summary>
+ public interface IHttpServer
+ {
+ /// <summary>
+ /// The <see cref="HttpConfig"/> for the current server
+ /// </summary>
+ HttpConfig Config { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the server is listening for connections
+ /// </summary>
+ bool Running { get; }
+
+ /// <summary>
+ /// Begins listening for connections on configured interfaces for configured hostnames.
+ /// </summary>
+ /// <param name="cancellationToken">A token used to stop listening for incomming connections and close all open websockets</param>
+ /// <returns>A task that resolves when the server has exited</returns>
+ Task Start(CancellationToken cancellationToken);
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs b/lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs
index f4a5491..7174a99 100644
--- a/lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs
+++ b/lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Transport.SimpleTCP
@@ -137,7 +137,7 @@ namespace VNLib.Net.Transport.Tcp
{
//Call sync
Task closing = Transport.CloseAsync();
- closing.Wait();
+ closing.GetAwaiter().GetResult();
}
public override ValueTask DisposeAsync()
diff --git a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs
index 45282b3..ae0c522 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs
@@ -39,7 +39,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// </summary>
public sealed class HttpServiceStack : VnDisposeable
{
- private readonly LinkedList<HttpServer> _servers;
+ private readonly LinkedList<IHttpServer> _servers;
private readonly ServiceDomain _serviceDomain;
private CancellationTokenSource? _cts;
@@ -48,7 +48,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// <summary>
/// A collection of all loaded servers
/// </summary>
- public IReadOnlyCollection<HttpServer> Servers => _servers;
+ public IReadOnlyCollection<IHttpServer> Servers => _servers;
/// <summary>
/// The service domain's plugin controller
@@ -60,7 +60,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// generate servers to listen for services exposed by the
/// specified host context
/// </summary>
- internal HttpServiceStack(LinkedList<HttpServer> servers, ServiceDomain serviceDomain)
+ internal HttpServiceStack(LinkedList<IHttpServer> servers, ServiceDomain serviceDomain)
{
_servers = servers;
_serviceDomain = serviceDomain;
diff --git a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs
index 0b75031..25b6d5f 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs
@@ -23,10 +23,11 @@
*/
using System;
-using System.Linq;
+using System.Threading.Tasks;
using System.Collections.Generic;
using VNLib.Net.Http;
+using VNLib.Utils.Logging;
namespace VNLib.Plugins.Essentials.ServiceStack
{
@@ -36,58 +37,123 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// </summary>
public sealed class HttpServiceStackBuilder
{
- private readonly LinkedList<HttpServer> _servers;
-
/// <summary>
- /// The built <see cref="HttpServiceStack"/>
+ /// Initializes a new <see cref="HttpServiceStack"/> that will
+ /// generate servers to listen for services exposed by the
+ /// specified host context
/// </summary>
- public HttpServiceStack ServiceStack { get; }
+ public HttpServiceStackBuilder()
+ {}
+
+ private Action<ICollection<IServiceHost>>? _hostBuilder;
+ private Func<ServiceGroup, IHttpServer>? _getServers;
/// <summary>
- /// Gets the underlying <see cref="ServiceDomain"/>
+ /// Uses the supplied callback to get a collection of virtual hosts
+ /// to build the current domain with
/// </summary>
- public ServiceDomain ServiceDomain { get; }
+ /// <param name="hostBuilder">The callback method to build virtual hosts</param>
+ /// <returns>A value that indicates if any virtual hosts were successfully loaded</returns>
+ public HttpServiceStackBuilder WithDomainBuilder(Action<ICollection<IServiceHost>> hostBuilder)
+ {
+ _hostBuilder = hostBuilder;
+ return this;
+ }
/// <summary>
- /// Initializes a new <see cref="HttpServiceStack"/> that will
- /// generate servers to listen for services exposed by the
- /// specified host context
+ /// Spcifies a callback function that builds <see cref="IHttpServer"/> instances from the hosts
/// </summary>
- public HttpServiceStackBuilder()
+ /// <param name="getServers">A callback method that gets the http server implementation for the service group</param>
+ public HttpServiceStackBuilder WithHttp(Func<ServiceGroup, IHttpServer> getServers)
{
- ServiceDomain = new();
- _servers = new();
- ServiceStack = new(_servers, ServiceDomain);
+ _getServers = getServers;
+ return this;
}
/// <summary>
- /// Builds all http servers from
+ /// Builds the new <see cref="HttpServiceStack"/> from the configured callbacks, WITHOUT loading plugins
/// </summary>
- /// <param name="config">The http server configuration to user for servers</param>
- /// <param name="getTransports">A callback method that gets the transport provider for the given host group</param>
- public void BuildServers(in HttpConfig config, Func<ServiceGroup, ITransportProvider> getTransports)
+ /// <returns>The newly constructed <see cref="HttpServiceStack"/> that may be used to manage your http services</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public HttpServiceStack Build()
{
- //enumerate hosts groups
- foreach (ServiceGroup hosts in ServiceDomain.ServiceGroups)
+ _ = _hostBuilder ?? throw new ArgumentNullException("WithDomainBuilder", "You have not configured a service domain configuration callback");
+ _ = _getServers ?? throw new ArgumentNullException("WithHttp", "You have not configured a IHttpServer configuration callback");
+
+ //Inint the service domain
+ ServiceDomain sd = new();
+ try
{
- //get transport for provider
- ITransportProvider transport = getTransports.Invoke(hosts);
+ if (!sd.BuildDomain(_hostBuilder))
+ {
+ throw new ArgumentException("Failed to configure the service domain, you must expose at least one service host");
+ }
+
+ LinkedList<IHttpServer> servers = new();
- //Create new server
- HttpServer server = new(config, transport, hosts.Hosts.Select(static h => h.Processor as IWebRoot));
+ //enumerate hosts groups
+ foreach (ServiceGroup hosts in sd.ServiceGroups)
+ {
+ //Create new server
+ IHttpServer server = _getServers.Invoke(hosts);
- //Add server to internal list
- _servers.AddLast(server);
+ //Add server to internal list
+ servers.AddLast(server);
+ }
+
+ //Return the service stack
+ return new HttpServiceStack(servers, sd);
+ }
+ catch
+ {
+ sd.Dispose();
+ throw;
}
}
/// <summary>
- /// Releases any resources that may be held by the <see cref="ServiceDomain"/>
- /// incase of an error
+ /// Builds the new <see cref="HttpServiceStack"/> from configured callbacks, AND loads your plugin environment
+ /// asynchronously
/// </summary>
- public void ReleaseOnError()
+ /// <returns>The newly constructed <see cref="HttpServiceStack"/> that may be used to manage your http services</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public async Task<HttpServiceStack> BuildAsync(PluginLoadConfiguration config, ILogProvider appLog)
{
- ServiceStack.Dispose();
+ _ = _hostBuilder ?? throw new ArgumentNullException("WithDomainBuilder", "You have not configured a service domain configuration callback");
+ _ = _getServers ?? throw new ArgumentNullException("WithHttp", "You have not configured a IHttpServer configuration callback");
+
+ //Inint the service domain
+ ServiceDomain sd = new();
+ try
+ {
+ if (!sd.BuildDomain(_hostBuilder))
+ {
+ throw new ArgumentException("Failed to configure the service domain, you must expose at least one service host");
+ }
+
+ //Load plugins async
+ await sd.PluginManager.LoadPluginsAsync(config, appLog);
+
+ LinkedList<IHttpServer> servers = new();
+
+ //enumerate hosts groups
+ foreach (ServiceGroup hosts in sd.ServiceGroups)
+ {
+ //Create new server
+ IHttpServer server = _getServers.Invoke(hosts);
+
+ //Add server to internal list
+ servers.AddLast(server);
+ }
+
+ //Return the service stack
+ return new HttpServiceStack(servers, sd);
+ }
+ catch
+ {
+ sd.Dispose();
+ throw;
+ }
}
}
}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginLoadConfiguration.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginLoadConfiguration.cs
index 4974e71..73cb201 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/PluginLoadConfiguration.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/PluginLoadConfiguration.cs
@@ -51,7 +51,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// The optional host configuration file to merge with plugin config
/// to pass to the loading plugin.
/// </summary>
- public readonly JsonDocument? HostConfig { get; init; }
+ public readonly JsonElement? HostConfig { get; init; }
/// <summary>
/// Passed to the underlying <see cref="RuntimePluginLoader"/>
diff --git a/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs b/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs
index 13cff7b..33da10e 100644
--- a/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs
+++ b/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs
@@ -53,7 +53,7 @@ namespace VNLib.Plugins.Essentials.Accounts
/// <summary>
/// The size in bytes of the random passwords generated when invoking the <see cref="SetRandomPasswordAsync(PasswordHashing, IUserManager, IUser, int)"/>
/// </summary>
- public const int RANDOM_PASS_SIZE = 128;
+ public const int RANDOM_PASS_SIZE = 240;
/// <summary>
/// The origin string of a local user account. This value will be set if an
@@ -409,8 +409,7 @@ namespace VNLib.Plugins.Essentials.Accounts
}
/// <summary>
- /// Attempts to encrypt the supplied data with session stored client information. The user must
- /// be authorized
+ /// Attempts to encrypt client data against the supplied security information instance, without a secured session.
/// </summary>
/// <param name="entity"></param>
/// <param name="secInfo">Used for unauthorized connections to encrypt client data based on client security info</param>
diff --git a/lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs b/lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs
index 7411789..dfb94f7 100644
--- a/lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs
+++ b/lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials
@@ -54,32 +54,26 @@ namespace VNLib.Plugins.Essentials.Endpoints
try
{
ERRNO preProc = PreProccess(entity);
+
if (preProc == ERRNO.E_FAIL)
{
return VfReturnType.Forbidden;
}
+
//Entity was responded to by the pre-processor
if (preProc < 0)
{
return VfReturnType.VirtualSkip;
}
+
//If websockets are quested allow them to be processed in a logged-in/secure context
if (entity.Server.IsWebSocketRequest)
{
return await WebsocketRequestedAsync(entity);
}
- ValueTask<VfReturnType> op = entity.Server.Method switch
- {
- //Get request to get account
- HttpMethod.GET => GetAsync(entity),
- HttpMethod.POST => PostAsync(entity),
- HttpMethod.DELETE => DeleteAsync(entity),
- HttpMethod.PUT => PutAsync(entity),
- HttpMethod.PATCH => PatchAsync(entity),
- HttpMethod.OPTIONS => OptionsAsync(entity),
- _ => AlternateMethodAsync(entity, entity.Server.Method),
- };
- return await op;
+
+ //Call process method
+ return await OnProcessAsync(entity);
}
catch (InvalidJsonRequestException ije)
{
@@ -178,6 +172,29 @@ namespace VNLib.Plugins.Essentials.Endpoints
}
/// <summary>
+ /// Called by the process method to process a connection after it has been pre-processed.
+ /// By default, this method simply selects the request method type and invokes the desired
+ /// handler
+ /// </summary>
+ /// <param name="entity">The entity to process</param>
+ /// <returns>A value task that completes when the processing has completed</returns>
+ protected virtual ValueTask<VfReturnType> OnProcessAsync(HttpEntity entity)
+ {
+ ValueTask<VfReturnType> op = entity.Server.Method switch
+ {
+ //Get request to get account
+ HttpMethod.GET => GetAsync(entity),
+ HttpMethod.POST => PostAsync(entity),
+ HttpMethod.DELETE => DeleteAsync(entity),
+ HttpMethod.PUT => PutAsync(entity),
+ HttpMethod.PATCH => PatchAsync(entity),
+ HttpMethod.OPTIONS => OptionsAsync(entity),
+ _ => AlternateMethodAsync(entity, entity.Server.Method),
+ };
+ return op;
+ }
+
+ /// <summary>
/// This method gets invoked when an incoming POST request to the endpoint has been requested.
/// </summary>
/// <param name="entity">The entity to be processed</param>
diff --git a/lib/Plugins.Essentials/src/Endpoints/VirtualEndpoint.cs b/lib/Plugins.Essentials/src/Endpoints/VirtualEndpoint.cs
index 5beb4b9..4ffe288 100644
--- a/lib/Plugins.Essentials/src/Endpoints/VirtualEndpoint.cs
+++ b/lib/Plugins.Essentials/src/Endpoints/VirtualEndpoint.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials
@@ -42,7 +42,7 @@ namespace VNLib.Plugins.Essentials.Endpoints
/// <summary>
/// An <see cref="ILogProvider"/> to write logs to
/// </summary>
- protected ILogProvider Log { get; private set; }
+ protected ILogProvider Log { get; set; }
/// <summary>
/// Sets the log and path and checks the values
@@ -61,6 +61,7 @@ namespace VNLib.Plugins.Essentials.Endpoints
//Store log
Log = log ?? throw new ArgumentNullException(nameof(log));
}
+
///<inheritdoc/>
public abstract ValueTask<VfReturnType> Process(T entity);
}
diff --git a/lib/Plugins.Essentials/src/EventProcessor.cs b/lib/Plugins.Essentials/src/EventProcessor.cs
index 820af30..a207e35 100644
--- a/lib/Plugins.Essentials/src/EventProcessor.cs
+++ b/lib/Plugins.Essentials/src/EventProcessor.cs
@@ -503,15 +503,19 @@ namespace VNLib.Plugins.Essentials
default:
break;
}
+
//If the file was not set or the request method is not a GET (or HEAD), return not-found
if (filename == null || (entity.Server.Method & (HttpMethod.GET | HttpMethod.HEAD)) == 0)
{
CloseWithError(HttpStatusCode.NotFound, entity);
return;
}
+
DateTime fileLastModified = File.GetLastWriteTimeUtc(filename);
+
//See if the last modifed header was set
DateTimeOffset? ifModifedSince = entity.Server.LastModified();
+
//If the header was set, check the date, if the file has been modified since, continue sending the file
if (ifModifedSince.HasValue && ifModifedSince.Value > fileLastModified)
{
@@ -519,20 +523,24 @@ namespace VNLib.Plugins.Essentials
entity.CloseResponse(HttpStatusCode.NotModified);
return;
}
+
//Get the content type of he file
ContentType fileType = HttpHelpers.GetContentTypeFromFile(filename);
+
//Make sure the client accepts the content type
if (entity.Server.Accepts(fileType))
{
//set last modified time as the files last write time
entity.Server.LastModified(fileLastModified);
+
//try to open the selected file for reading and allow sharing
FileStream fs = new (filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+
//Check for range
if (entity.Server.Range != null && entity.Server.Range.Item1 > 0)
{
//Seek the stream to the specified position
- fs.Position = entity.Server.Range.Item1;
+ fs.Seek(entity.Server.Range.Item1, SeekOrigin.Begin);
entity.CloseResponse(HttpStatusCode.PartialContent, fileType, fs);
}
else
@@ -540,13 +548,11 @@ namespace VNLib.Plugins.Essentials
//send the whole file
entity.CloseResponse(HttpStatusCode.OK, fileType, fs);
}
- return;
}
else
{
//Unacceptable
CloseWithError(HttpStatusCode.NotAcceptable, entity);
- return;
}
}
catch (IOException ioe)
@@ -557,7 +563,7 @@ namespace VNLib.Plugins.Essentials
}
catch (Exception ex)
{
- Log.Information(ex, "Unhandled exception during file opening.");
+ Log.Error(ex, "Unhandled exception during file opening.");
//Invoke the root error handler
CloseWithError(HttpStatusCode.InternalServerError, entity);
return;
diff --git a/lib/Plugins.Runtime/src/PluginController.cs b/lib/Plugins.Runtime/src/PluginController.cs
index f602492..53e0ae3 100644
--- a/lib/Plugins.Runtime/src/PluginController.cs
+++ b/lib/Plugins.Runtime/src/PluginController.cs
@@ -25,7 +25,6 @@
using System;
using System.Linq;
using System.Text.Json;
-using System.Threading;
using System.Reflection;
using System.Collections.Generic;
@@ -143,7 +142,6 @@ namespace VNLib.Plugins.Runtime
{
//Always
_plugins.Clear();
-
}
}
}
diff --git a/lib/Plugins.Runtime/src/RuntimePluginLoader.cs b/lib/Plugins.Runtime/src/RuntimePluginLoader.cs
index 698ce56..2a03a3d 100644
--- a/lib/Plugins.Runtime/src/RuntimePluginLoader.cs
+++ b/lib/Plugins.Runtime/src/RuntimePluginLoader.cs
@@ -71,7 +71,7 @@ namespace VNLib.Plugins.Runtime
/// The <paramref name="log"/> argument may be null if <paramref name="unloadable"/> is false
/// </remarks>
/// <exception cref="ArgumentNullException"></exception>
- public RuntimePluginLoader(string pluginPath, JsonDocument? hostConfig = null, ILogProvider? log = null, bool unloadable = false, bool hotReload = false)
+ public RuntimePluginLoader(string pluginPath, JsonElement? hostConfig = null, ILogProvider? log = null, bool unloadable = false, bool hotReload = false)
:this(
new PluginConfig(pluginPath)
{
@@ -92,13 +92,13 @@ namespace VNLib.Plugins.Runtime
/// <param name="hostConfig">The host/process configuration DOM</param>
/// <param name="log">A log provider to write plugin unload log events to</param>
/// <exception cref="ArgumentNullException"></exception>
- public RuntimePluginLoader(PluginConfig config, JsonDocument? hostConfig, ILogProvider? log)
+ public RuntimePluginLoader(PluginConfig config, JsonElement? hostConfig, ILogProvider? log)
{
//Shared types is required so the default load context shares types
config.PreferSharedTypes = true;
- //Default to empty config if null
- HostConfig = hostConfig ?? JsonDocument.Parse("{}");
+ //Default to empty config if null, otherwise clone a copy of the host config element
+ HostConfig = hostConfig.HasValue ? Clone(hostConfig.Value) : JsonDocument.Parse("{}");
Loader = new(config);
PluginPath = config.MainAssemblyPath;
@@ -203,7 +203,24 @@ namespace VNLib.Plugins.Runtime
{
Controller.Dispose();
Loader.Dispose();
+ HostConfig.Dispose();
}
+
+ private static JsonDocument Clone(JsonElement hostConfig)
+ {
+ //Crate ms to write the current doc data to
+ using VnMemoryStream ms = new();
+
+ using (Utf8JsonWriter writer = new(ms))
+ {
+ hostConfig.WriteTo(writer);
+ }
+
+ //Reset ms
+ ms.Seek(0, SeekOrigin.Begin);
+
+ return JsonDocument.Parse(ms);
+ }
}
} \ No newline at end of file
diff --git a/lib/Utils/README.md b/lib/Utils/README.md
index 888ac3e..34e28d5 100644
--- a/lib/Utils/README.md
+++ b/lib/Utils/README.md
@@ -28,7 +28,8 @@ Allocator selection has been updated to abstract the unmanaged heap loading to a
There are two types of heap architectures that are in use within the Memory namespace. Global/Shared and Private/First Class. They generally make the following assumptions
-**Shared Heap** - Prefers performance over isolation, consumers expect calls to have minimal multi-threaded performance penalties at the cost of isolation
+**Shared Heap** - Prefers performance over isolation, consumers expect calls to have minimal multi-threaded performance penalties at the cost of isolation.
+
**Private Heap** - Prefers isolation over performance, consumers assume calls may have multi-threaded performance penalties for the benefit of isolation.
On heap creation, the `UnmanagedHeapDescriptor` structure's `HeapCreationFlags` will have the IsShared flag set if the desired heap is the global heap, or a private heap in the absence of said flag. By default **ALL** heaps are assumed **not** thread safe, and synchronization is provided by the `UnmanagedHeapBase` class, and as such, the UseSynchronization flag is always present when using the `InitializeNewHeapForProcess` method call.
@@ -50,7 +51,7 @@ Setting the `VNLIB_SHARED_HEAP_FILE_PATH` environment variable will instruct the
If you don't want to use a custom heap implementation, the library has safe fallbacks for all platforms. On Windows the PrivateHeap api is used by default. The shared heap, again, prefers performance and will use the process heap returned from `GetProcessHeap()`, instead of creating a private heap that requires synchronization penalties. On all other platforms the fallback will be the .NET NativeMemory allocator, which is cross platform, but does **not** actually implement a "private" heap. So that means on non-Windows platforms unless you select your own heap, isolation is not an option.
### Heap Diagnostics
-The Memory.Diagnostics namespace was added to provide a wrapper for tracking IUnmanagedHeap memory allocations. Diagnostics can be enabled for the SharedHeap by setting the `VNLIB_SHARED_HEAP_DIAGNOSTICS` environment variable to "1". When enabled, calling MemoryUtil.GetSharedHeapStats() will return the heap's current statistics, otherwise an empty struct is returned. The Shared heap diagnostics are disabled by default.
+The Memory.Diagnostics namespace was added to provide a wrapper for tracking IUnmanagedHeap memory allocations. Diagnostics can be enabled for the SharedHeap by setting the `VNLIB_SHARED_HEAP_DIAGNOSTICS` environment variable to "1". When enabled, calling `MemoryUtil.GetSharedHeapStats()` will return the heap's current statistics, otherwise an empty struct is returned. The Shared heap diagnostics are disabled by default.
### Other notes
Generally for internal library data structures that require memory allocation, a constructor override or a static method will consume a heap instance so you may pass your own heap instance or the Shared heap.