From 12391e9a207b60b41a074600fc2373ad3eb1c3ab Mon Sep 17 00:00:00 2001 From: vnugent Date: Wed, 26 Jun 2024 21:01:15 -0400 Subject: feat(server): Server arch update, Memory struct access --- lib/Net.Http/src/Core/HttpServerBase.cs | 105 ++++++++++++++++----- lib/Net.Http/src/Core/HttpServerProcessing.cs | 16 ++-- lib/Net.Http/src/Core/HttpTransportBinding.cs | 39 ++++++++ lib/Net.Http/src/Core/Request/HttpInputStream.cs | 1 - lib/Net.Http/src/Core/Request/HttpRequest.cs | 1 - lib/Net.Http/src/Core/Response/TransportManager.cs | 99 ------------------- lib/Net.Http/src/Core/TransportManager.cs | 99 +++++++++++++++++++ lib/Net.Http/src/HttpConfig.cs | 4 +- 8 files changed, 232 insertions(+), 132 deletions(-) create mode 100644 lib/Net.Http/src/Core/HttpTransportBinding.cs delete mode 100644 lib/Net.Http/src/Core/Response/TransportManager.cs create mode 100644 lib/Net.Http/src/Core/TransportManager.cs (limited to 'lib/Net.Http') diff --git a/lib/Net.Http/src/Core/HttpServerBase.cs b/lib/Net.Http/src/Core/HttpServerBase.cs index 5954057..1ccace9 100644 --- a/lib/Net.Http/src/Core/HttpServerBase.cs +++ b/lib/Net.Http/src/Core/HttpServerBase.cs @@ -35,6 +35,21 @@ * to function safely with async programming practices. */ +/* + * 6-26-2024 + * + * Server has been transformed to ultilse a single configuration to listen + * on a map of transport servers and isolate those connections to individual + * virtual hosts. It allows multiple virtual hosts to be mapped to a single + * transport server, but also allow a many-to-many relationship between + * transport servers and virtual hosts. + * + * The reason for this is HTTP server resource efficiency. A single HTTP server + * isolates its caching and memory pools. By sharing caches across transport + * bindings, we can still have the security isolation of transport : virtual host + * but share the resources of the server. + */ + using System; using System.Linq; using System.Threading; @@ -75,9 +90,8 @@ namespace VNLib.Net.Http /// internal static readonly Memory WriteOnlyScratchBuffer = new byte[64 * 1024]; - private readonly ITransportProvider[] Transports; - private readonly FrozenDictionary ServerRoots; - private readonly IWebRoot? _wildcardRoot; + private readonly ListenerState[] Transports; + private readonly HttpConfig _config; #region caches @@ -115,21 +129,20 @@ namespace VNLib.Net.Http /// Immutable data structures are initialzed. /// /// The configuration used to create the instance - /// An enumeration of transports to listen for connections on - /// A collection of s that route incomming connetctions + /// One to many relational mapping between a transport provider and it's routes /// - public HttpServer(HttpConfig config, IEnumerable transports, IEnumerable sites) + public HttpServer(HttpConfig config, IEnumerable bindings) { //Validate the configuration ValidateConfig(in config); _config = config; - //Configure roots and their directories - ServerRoots = sites.ToFrozenDictionary(static r => r.Hostname, static tv => tv, StringComparer.OrdinalIgnoreCase); + //Compile and store the timeout keepalive header - KeepAliveTimeoutHeaderValue = $"timeout={(int)_config.ConnectionKeepAlive.TotalSeconds}"; - - Transports = transports.ToArray(); + KeepAliveTimeoutHeaderValue = $"timeout={(int)_config.ConnectionKeepAlive.TotalSeconds}"; + + //Map transport listeners to their virtual hosts + Transports = MapListeners(bindings); //Cache supported compression methods, or none if compressor is null SupportedCompressionMethods = config.CompressorManager == null @@ -138,9 +151,6 @@ namespace VNLib.Net.Http //Create a new context store ContextStore = ObjectRental.CreateReusable(() => new HttpContext(this, SupportedCompressionMethods)); - - //Cache wildcard root - _wildcardRoot = ServerRoots.GetValueOrDefault(WILDCARD_KEY); } private static void ValidateConfig(in HttpConfig conf) @@ -232,6 +242,29 @@ namespace VNLib.Net.Http } } + private static ListenerState[] MapListeners(IEnumerable bindings) + { + /* + * Transform the bindings to individual http listeners + * which also requires a frozen mapping of hostnames to + * virtual host + */ + + return bindings.Select(static b => new ListenerState + { + OriginServer = b.Transport, + + Roots = b.Roots.ToFrozenDictionary( + static r => r.Hostname, + static tv => tv, + StringComparer.OrdinalIgnoreCase + ), + + //Yoink the wildcard route if it's set + DefaultRoute = b.Roots.FirstOrDefault(static r => string.Equals(r.Hostname, WILDCARD_KEY, StringComparison.OrdinalIgnoreCase)) + }).ToArray(); + } + /// /// Begins listening for connections on configured interfaces for configured hostnames. /// @@ -246,7 +279,7 @@ namespace VNLib.Net.Http StopToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); //Start servers with the new token source before listening for connections - Array.ForEach(Transports, p => p.Start(StopToken.Token)); + Array.ForEach(Transports, p => p.OriginServer.Start(StopToken.Token)); //Listen to connections on all transports async IEnumerable runTasks = Transports.Select(ListenAsync); @@ -254,6 +287,7 @@ namespace VNLib.Net.Http //Set running flag and will be reset when all listening tasks are done Running = true; + //Calling WhenAll() will force the numeration and schedule listening tasks return Task.WhenAll(runTasks) .ContinueWith( OnAllStopped, @@ -263,7 +297,7 @@ namespace VNLib.Net.Http ); //Defer listening tasks to the task scheduler to avoid blocking this thread - Task ListenAsync(ITransportProvider tp) => Task.Run(() => ListenWorkerDoWork(tp), cancellationToken); + Task ListenAsync(ListenerState tp) => Task.Run(() => ListenWorkerDoWork(tp), cancellationToken); void OnAllStopped(Task _) => Running = false; } @@ -271,10 +305,9 @@ namespace VNLib.Net.Http /* * A worker task that listens for connections from the transport */ - private async Task ListenWorkerDoWork(ITransportProvider transport) + private async Task ListenWorkerDoWork(ListenerState state) { - //Set running flag - Running = true; + state.Running = true; _config.ServerLog.Information("HTTP server {hc} listening for connections", GetHashCode()); @@ -284,10 +317,10 @@ namespace VNLib.Net.Http try { //Listen for new connection - ITransportContext ctx = await transport.AcceptAsync(StopToken!.Token); + ITransportContext ctx = await state.OriginServer.AcceptAsync(StopToken!.Token); //Try to dispatch the received event - _ = DataReceivedAsync(ctx).ConfigureAwait(false); + _ = DataReceivedAsync(state, ctx).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -303,8 +336,8 @@ namespace VNLib.Net.Http //Clear all caches before leaving to aid gc CacheHardClear(); - //Clear running flag - Running = false; + state.Running = false; + _config.ServerLog.Information("HTTP server {hc} exiting", GetHashCode()); } @@ -340,5 +373,31 @@ namespace VNLib.Net.Http break; } } + + private sealed class ListenerState + { + /* + * Indexers ensure correct access during debug builds, but fields + * can be used directly for tiny performance boost in release builds + */ + + public bool Running; + +#if DEBUG + + public required ITransportProvider OriginServer { get; init; } + + public required FrozenDictionary Roots { get; init; } + + public required IWebRoot? DefaultRoute { get; init; } + +#else + public required ITransportProvider OriginServer; + public required FrozenDictionary Roots; + public required IWebRoot? DefaultRoute; +#endif + + } + } } \ No newline at end of file diff --git a/lib/Net.Http/src/Core/HttpServerProcessing.cs b/lib/Net.Http/src/Core/HttpServerProcessing.cs index dfa006f..f5dbbc7 100644 --- a/lib/Net.Http/src/Core/HttpServerProcessing.cs +++ b/lib/Net.Http/src/Core/HttpServerProcessing.cs @@ -47,7 +47,7 @@ namespace VNLib.Net.Http private int OpenConnectionCount; //Event handler method for processing incoming data events - private async Task DataReceivedAsync(ITransportContext transportContext) + private async Task DataReceivedAsync(ListenerState listenState, ITransportContext transportContext) { Interlocked.Increment(ref OpenConnectionCount); @@ -77,7 +77,7 @@ namespace VNLib.Net.Http //Return read timeout to active connection timeout after data is received stream.ReadTimeout = _config.ActiveConnectionRecvTimeout; - bool keepAlive = await ProcessHttpEventAsync(context); + bool keepAlive = await ProcessHttpEventAsync(listenState, context); //If not keepalive, exit the listening loop and clean up connection if (!keepAlive) @@ -169,9 +169,10 @@ namespace VNLib.Net.Http /// /// Main event handler for all incoming connections /// + /// /// Reusable context object [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private async Task ProcessHttpEventAsync(HttpContext context) + private async Task ProcessHttpEventAsync(ListenerState listenState, HttpContext context) { HttpPerfCounterState counter = default; @@ -201,7 +202,7 @@ namespace VNLib.Net.Http return false; } - bool processSuccess = await ProcessRequestAsync(context); + bool processSuccess = await ProcessRequestAsync(listenState, context); #if DEBUG static void WriteConnectionDebugLog(HttpServer server, HttpContext context) @@ -382,10 +383,13 @@ namespace VNLib.Net.Http } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private async Task ProcessRequestAsync(HttpContext context) + private async Task ProcessRequestAsync(ListenerState listenState, HttpContext context) { //Get the server root for the specified location or fallback to a wildcard host if one is selected - IWebRoot? root = ServerRoots!.GetValueOrDefault(context.Request.State.Location.DnsSafeHost, _wildcardRoot); + IWebRoot? root = listenState.Roots.GetValueOrDefault( + context.Request.State.Location.DnsSafeHost, + listenState.DefaultRoute + ); if (root == null) { diff --git a/lib/Net.Http/src/Core/HttpTransportBinding.cs b/lib/Net.Http/src/Core/HttpTransportBinding.cs new file mode 100644 index 0000000..eda83aa --- /dev/null +++ b/lib/Net.Http/src/Core/HttpTransportBinding.cs @@ -0,0 +1,39 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Net.Http +* File: HttpTransportBinding.cs +* +* HttpTransportBinding.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.Collections.Generic; + +namespace VNLib.Net.Http +{ + /// + /// Presents a one-to-many relationship between a transport provider and it's virtual hosts + /// + /// The transport to listen for incomming connections on + /// The enumeration of web roots that will route connections + /// + /// An HTTP server accepts a collection of these bindings to allow for a many-to-many + /// relationship between transport providers and virtual hosts. + /// + public sealed record HttpTransportBinding(ITransportProvider Transport, IEnumerable Roots); +} \ No newline at end of file diff --git a/lib/Net.Http/src/Core/Request/HttpInputStream.cs b/lib/Net.Http/src/Core/Request/HttpInputStream.cs index 9ad0218..29dda2d 100644 --- a/lib/Net.Http/src/Core/Request/HttpInputStream.cs +++ b/lib/Net.Http/src/Core/Request/HttpInputStream.cs @@ -32,7 +32,6 @@ using System.Runtime.CompilerServices; using VNLib.Utils; using VNLib.Utils.Memory; using VNLib.Utils.Extensions; -using VNLib.Net.Http.Core.Response; namespace VNLib.Net.Http.Core { diff --git a/lib/Net.Http/src/Core/Request/HttpRequest.cs b/lib/Net.Http/src/Core/Request/HttpRequest.cs index 9263e0f..cbe6bc0 100644 --- a/lib/Net.Http/src/Core/Request/HttpRequest.cs +++ b/lib/Net.Http/src/Core/Request/HttpRequest.cs @@ -29,7 +29,6 @@ using System.Runtime.CompilerServices; using VNLib.Utils; using VNLib.Utils.Memory; using VNLib.Utils.Extensions; -using VNLib.Net.Http.Core.Response; namespace VNLib.Net.Http.Core { diff --git a/lib/Net.Http/src/Core/Response/TransportManager.cs b/lib/Net.Http/src/Core/Response/TransportManager.cs deleted file mode 100644 index 45efc4b..0000000 --- a/lib/Net.Http/src/Core/Response/TransportManager.cs +++ /dev/null @@ -1,99 +0,0 @@ -/* -* Copyright (c) 2024 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Net.Http -* File: TransportManager.cs -* -* TransportManager.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.IO; -using System.Buffers; -using System.Diagnostics; -using System.Threading.Tasks; - -namespace VNLib.Net.Http.Core.Response -{ - internal sealed class TransportManager - { - public bool IsBufferWriter; - -#nullable disable - -#if DEBUG - - private Stream _stream; - private IBufferWriter _asWriter; - - public Stream Stream - { - get - { - Debug.Assert(_stream != null, "Transport stream was accessed but was set to null"); - return _stream; - } - set => _stream = value; - } - - public IBufferWriter Writer - { - get - { - Debug.Assert(_asWriter != null, "Transport buffer writer accessed but the writer is null"); - return _asWriter; - } - set => _asWriter = value; - } - -#else - public Stream Stream; - public IBufferWriter Writer; -#endif - -#nullable restore - - public Task FlushAsync() => Stream.FlushAsync(); - - /// - /// Assigns a new transport stream to the wrapper - /// as a new connection is assigned to write responses to - /// - /// The transport stream to wrap - public void OnNewConnection(Stream transportStream) - { - Stream = transportStream; - - //Capture a buffer writer if the incoming stream supports direct writing - if (transportStream is IBufferWriter bw) - { - Writer = bw; - IsBufferWriter = true; - } - } - - /// - /// Closes the current connection and resets the transport stream - /// - public void OnRelease() - { - Stream = null; - Writer = null; - IsBufferWriter = false; - } - } -} \ No newline at end of file diff --git a/lib/Net.Http/src/Core/TransportManager.cs b/lib/Net.Http/src/Core/TransportManager.cs new file mode 100644 index 0000000..2632fc5 --- /dev/null +++ b/lib/Net.Http/src/Core/TransportManager.cs @@ -0,0 +1,99 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Net.Http +* File: TransportManager.cs +* +* TransportManager.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.IO; +using System.Buffers; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace VNLib.Net.Http.Core +{ + internal sealed class TransportManager + { + public bool IsBufferWriter; + +#nullable disable + +#if DEBUG + + private Stream _stream; + private IBufferWriter _asWriter; + + public Stream Stream + { + get + { + Debug.Assert(_stream != null, "Transport stream was accessed but was set to null"); + return _stream; + } + set => _stream = value; + } + + public IBufferWriter Writer + { + get + { + Debug.Assert(_asWriter != null, "Transport buffer writer accessed but the writer is null"); + return _asWriter; + } + set => _asWriter = value; + } + +#else + public Stream Stream; + public IBufferWriter Writer; +#endif + +#nullable restore + + public Task FlushAsync() => Stream.FlushAsync(); + + /// + /// Assigns a new transport stream to the wrapper + /// as a new connection is assigned to write responses to + /// + /// The transport stream to wrap + public void OnNewConnection(Stream transportStream) + { + Stream = transportStream; + + //Capture a buffer writer if the incoming stream supports direct writing + if (transportStream is IBufferWriter bw) + { + Writer = bw; + IsBufferWriter = true; + } + } + + /// + /// Closes the current connection and resets the transport stream + /// + public void OnRelease() + { + Stream = null; + Writer = null; + IsBufferWriter = false; + } + } +} \ No newline at end of file diff --git a/lib/Net.Http/src/HttpConfig.cs b/lib/Net.Http/src/HttpConfig.cs index ff0434f..aa6e34a 100644 --- a/lib/Net.Http/src/HttpConfig.cs +++ b/lib/Net.Http/src/HttpConfig.cs @@ -77,12 +77,12 @@ namespace VNLib.Net.Http /// /// A log provider that all server related log entiries will be written to /// - public ILogProvider ServerLog { get; init; } + public readonly ILogProvider ServerLog { get; init; } /// /// Server memory pool to use for allocating buffers /// - public IHttpMemoryPool MemoryPool { get; init; } + public readonly IHttpMemoryPool MemoryPool { get; init; } /// /// The absolute request entity body size limit in bytes -- cgit