diff options
Diffstat (limited to 'lib/Net.Http')
-rw-r--r-- | lib/Net.Http/src/Core/HttpServerBase.cs | 105 | ||||
-rw-r--r-- | lib/Net.Http/src/Core/HttpServerProcessing.cs | 16 | ||||
-rw-r--r-- | lib/Net.Http/src/Core/HttpTransportBinding.cs | 39 | ||||
-rw-r--r-- | lib/Net.Http/src/Core/Request/HttpInputStream.cs | 1 | ||||
-rw-r--r-- | lib/Net.Http/src/Core/Request/HttpRequest.cs | 1 | ||||
-rw-r--r-- | lib/Net.Http/src/Core/TransportManager.cs (renamed from lib/Net.Http/src/Core/Response/TransportManager.cs) | 2 | ||||
-rw-r--r-- | lib/Net.Http/src/HttpConfig.cs | 4 |
7 files changed, 134 insertions, 34 deletions
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 /// </summary> internal static readonly Memory<byte> WriteOnlyScratchBuffer = new byte[64 * 1024]; - private readonly ITransportProvider[] Transports; - private readonly FrozenDictionary<string, IWebRoot> 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. /// </summary> /// <param name="config">The configuration used to create the instance</param> - /// <param name="transports">An enumeration of transports to listen for connections on</param> - /// <param name="sites">A collection of <see cref="IWebRoot"/>s that route incomming connetctions</param> + /// <param name="bindings">One to many relational mapping between a transport provider and it's routes</param> /// <exception cref="ArgumentException"></exception> - public HttpServer(HttpConfig config, IEnumerable<ITransportProvider> transports, IEnumerable<IWebRoot> sites) + public HttpServer(HttpConfig config, IEnumerable<HttpTransportBinding> 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<HttpTransportBinding> 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(); + } + /// <summary> /// Begins listening for connections on configured interfaces for configured hostnames. /// </summary> @@ -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<Task> 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<string, IWebRoot> Roots { get; init; } + + public required IWebRoot? DefaultRoute { get; init; } + +#else + public required ITransportProvider OriginServer; + public required FrozenDictionary<string, IWebRoot> 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 /// <summary> /// Main event handler for all incoming connections /// </summary> + /// <param name="listenState"></param> /// <param name="context">Reusable context object</param> [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private async Task<bool> ProcessHttpEventAsync(HttpContext context) + private async Task<bool> 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<bool> ProcessRequestAsync(HttpContext context) + private async Task<bool> 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 +{ + /// <summary> + /// Presents a one-to-many relationship between a transport provider and it's virtual hosts + /// </summary> + /// <param name="Transport">The transport to listen for incomming connections on</param> + /// <param name="Roots">The enumeration of web roots that will route connections</param> + /// <remarks> + /// An HTTP server accepts a collection of these bindings to allow for a many-to-many + /// relationship between transport providers and virtual hosts. + /// </remarks> + public sealed record HttpTransportBinding(ITransportProvider Transport, IEnumerable<IWebRoot> 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/TransportManager.cs index 45efc4b..2632fc5 100644 --- a/lib/Net.Http/src/Core/Response/TransportManager.cs +++ b/lib/Net.Http/src/Core/TransportManager.cs @@ -27,7 +27,7 @@ using System.Buffers; using System.Diagnostics; using System.Threading.Tasks; -namespace VNLib.Net.Http.Core.Response +namespace VNLib.Net.Http.Core { internal sealed class TransportManager { 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 /// <summary> /// A log provider that all server related log entiries will be written to /// </summary> - public ILogProvider ServerLog { get; init; } + public readonly ILogProvider ServerLog { get; init; } /// <summary> /// Server memory pool to use for allocating buffers /// </summary> - public IHttpMemoryPool MemoryPool { get; init; } + public readonly IHttpMemoryPool MemoryPool { get; init; } /// <summary> /// The absolute request entity body size limit in bytes |