From 228e1b59a2fc24c2e4ce57aa8171a13a1a18c199 Mon Sep 17 00:00:00 2001 From: vnugent Date: Sat, 8 Apr 2023 16:09:51 -0400 Subject: Minor updates and API changes --- lib/Net.Http/src/Core/ConnectionInfo.cs | 34 +++--- lib/Net.Http/src/Core/HttpContext.cs | 11 +- lib/Net.Http/src/Core/HttpServerBase.cs | 14 ++- lib/Net.Http/src/Core/HttpServerProcessing.cs | 72 +++++++----- .../src/Core/Request/HttpRequestExtensions.cs | 4 +- lib/Net.Http/src/Helpers/TransportReader.cs | 23 ++-- lib/Net.Http/src/IHttpServer.cs | 52 +++++++++ .../src/ReusableNetworkStream.cs | 4 +- .../src/HttpServiceStack.cs | 6 +- .../src/HttpServiceStackBuilder.cs | 126 ++++++++++++++++----- .../src/PluginLoadConfiguration.cs | 2 +- .../src/Accounts/AccountUtils.cs | 5 +- .../src/Endpoints/ResourceEndpointBase.cs | 43 ++++--- .../src/Endpoints/VirtualEndpoint.cs | 5 +- lib/Plugins.Essentials/src/EventProcessor.cs | 14 ++- lib/Plugins.Runtime/src/PluginController.cs | 2 - lib/Plugins.Runtime/src/RuntimePluginLoader.cs | 25 +++- lib/Utils/README.md | 5 +- 18 files changed, 314 insertions(+), 133 deletions(-) create mode 100644 lib/Net.Http/src/IHttpServer.cs 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); } + /// 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 primary = contentType.AsSpan().SliceBeforeParam('/'); + + for (int i = 0; i < Context.Request.Accept.Count; i++) + { + //The the accept subtype + ReadOnlySpan 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 primary = contentType.AsSpan().SliceBeforeParam('/'); - ReadOnlySpan 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; } /// 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 /// /// Gets or sets the alternate application protocol to swtich to /// + /// + /// 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 + /// public IAlternateProtocol? AlternateProtocol { get; set; } private readonly ResponseWriter responseWriter; @@ -138,8 +143,6 @@ namespace VNLib.Net.Http.Core /// 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 /// - public sealed partial class HttpServer : ICacheHolder + public sealed partial class HttpServer : ICacheHolder, IHttpServer { /// /// The host key that determines a "wildcard" host, meaning the @@ -206,19 +206,19 @@ namespace VNLib.Net.Http /// /// Begins listening for connections on configured interfaces for configured hostnames. /// - /// A token used to stop listening for incomming connections and close all open websockets + /// A token used to stop listening for incomming connections and close all open websockets /// A task that resolves when the server has exited /// /// /// /// - 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.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 /// The describing the incoming connection /// Reusable context object [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private async Task ProcessHttpEventAsync(ITransportContext transportContext, HttpContext context) + private async Task 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 ProcessRequestAsync(HttpContext context, HttpStatusCode status) + private async ValueTask 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 for an incomming connection /// /// - /// The to attach the request to + /// The to attach the request to /// The default http version [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 { /// - public readonly Encoding Encoding => _encoding; + public readonly Encoding Encoding { get; } /// - public readonly ReadOnlyMemory LineTermination => _lineTermination; + public readonly ReadOnlyMemory LineTermination { get; } /// - 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 _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 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 +{ + /// + /// Provides an HTTP based application layer protocol server + /// + public interface IHttpServer + { + /// + /// The for the current server + /// + HttpConfig Config { get; } + + /// + /// Gets a value indicating whether the server is listening for connections + /// + bool Running { get; } + + /// + /// Begins listening for connections on configured interfaces for configured hostnames. + /// + /// A token used to stop listening for incomming connections and close all open websockets + /// A task that resolves when the server has exited + 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 /// public sealed class HttpServiceStack : VnDisposeable { - private readonly LinkedList _servers; + private readonly LinkedList _servers; private readonly ServiceDomain _serviceDomain; private CancellationTokenSource? _cts; @@ -48,7 +48,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// /// A collection of all loaded servers /// - public IReadOnlyCollection Servers => _servers; + public IReadOnlyCollection Servers => _servers; /// /// 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 /// - internal HttpServiceStack(LinkedList servers, ServiceDomain serviceDomain) + internal HttpServiceStack(LinkedList 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 /// public sealed class HttpServiceStackBuilder { - private readonly LinkedList _servers; - /// - /// The built + /// Initializes a new that will + /// generate servers to listen for services exposed by the + /// specified host context /// - public HttpServiceStack ServiceStack { get; } + public HttpServiceStackBuilder() + {} + + private Action>? _hostBuilder; + private Func? _getServers; /// - /// Gets the underlying + /// Uses the supplied callback to get a collection of virtual hosts + /// to build the current domain with /// - public ServiceDomain ServiceDomain { get; } + /// The callback method to build virtual hosts + /// A value that indicates if any virtual hosts were successfully loaded + public HttpServiceStackBuilder WithDomainBuilder(Action> hostBuilder) + { + _hostBuilder = hostBuilder; + return this; + } /// - /// Initializes a new that will - /// generate servers to listen for services exposed by the - /// specified host context + /// Spcifies a callback function that builds instances from the hosts /// - public HttpServiceStackBuilder() + /// A callback method that gets the http server implementation for the service group + public HttpServiceStackBuilder WithHttp(Func getServers) { - ServiceDomain = new(); - _servers = new(); - ServiceStack = new(_servers, ServiceDomain); + _getServers = getServers; + return this; } /// - /// Builds all http servers from + /// Builds the new from the configured callbacks, WITHOUT loading plugins /// - /// The http server configuration to user for servers - /// A callback method that gets the transport provider for the given host group - public void BuildServers(in HttpConfig config, Func getTransports) + /// The newly constructed that may be used to manage your http services + /// + 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 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; } } /// - /// Releases any resources that may be held by the - /// incase of an error + /// Builds the new from configured callbacks, AND loads your plugin environment + /// asynchronously /// - public void ReleaseOnError() + /// The newly constructed that may be used to manage your http services + /// + public async Task 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 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. /// - public readonly JsonDocument? HostConfig { get; init; } + public readonly JsonElement? HostConfig { get; init; } /// /// Passed to the underlying 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 /// /// The size in bytes of the random passwords generated when invoking the /// - public const int RANDOM_PASS_SIZE = 128; + public const int RANDOM_PASS_SIZE = 240; /// /// The origin string of a local user account. This value will be set if an @@ -409,8 +409,7 @@ namespace VNLib.Plugins.Essentials.Accounts } /// - /// 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. /// /// /// Used for unauthorized connections to encrypt client data based on client security info 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 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) { @@ -177,6 +171,29 @@ namespace VNLib.Plugins.Essentials.Endpoints return true; } + /// + /// 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 + /// + /// The entity to process + /// A value task that completes when the processing has completed + protected virtual ValueTask OnProcessAsync(HttpEntity entity) + { + ValueTask 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; + } + /// /// This method gets invoked when an incoming POST request to the endpoint has been requested. /// 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 /// /// An to write logs to /// - protected ILogProvider Log { get; private set; } + protected ILogProvider Log { get; set; } /// /// 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)); } + /// public abstract ValueTask 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 argument may be null if is false /// /// - 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 /// The host/process configuration DOM /// A log provider to write plugin unload log events to /// - 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. -- cgit