aboutsummaryrefslogtreecommitdiff
path: root/lib/Net.Http
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-06-26 21:01:15 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2024-06-26 21:01:15 -0400
commit12391e9a207b60b41a074600fc2373ad3eb1c3ab (patch)
tree5a3396a889a2226aaabdf9aee5cd7cb4f5b04e31 /lib/Net.Http
parent92e182ceaf843f8d859d38faa8b2c0ff53207ff6 (diff)
feat(server): Server arch update, Memory struct access
Diffstat (limited to 'lib/Net.Http')
-rw-r--r--lib/Net.Http/src/Core/HttpServerBase.cs105
-rw-r--r--lib/Net.Http/src/Core/HttpServerProcessing.cs16
-rw-r--r--lib/Net.Http/src/Core/HttpTransportBinding.cs39
-rw-r--r--lib/Net.Http/src/Core/Request/HttpInputStream.cs1
-rw-r--r--lib/Net.Http/src/Core/Request/HttpRequest.cs1
-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.cs4
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