aboutsummaryrefslogtreecommitdiff
path: root/lib/Net.Http/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Net.Http/src')
-rw-r--r--lib/Net.Http/src/AlternateProtocolBase.cs11
-rw-r--r--lib/Net.Http/src/Core/Buffering/ContextLockedBufferManager.cs103
-rw-r--r--lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs7
-rw-r--r--lib/Net.Http/src/Core/Buffering/SplitHttpBufferElement.cs8
-rw-r--r--lib/Net.Http/src/Core/HttpContext.cs88
-rw-r--r--lib/Net.Http/src/Core/HttpEncodedSegment.cs16
-rw-r--r--lib/Net.Http/src/Core/HttpEvent.cs9
-rw-r--r--lib/Net.Http/src/Core/HttpServerBase.cs174
-rw-r--r--lib/Net.Http/src/Core/HttpServerProcessing.cs88
-rw-r--r--lib/Net.Http/src/Core/HttpTransportBinding.cs39
-rw-r--r--lib/Net.Http/src/Core/IHttpContextInformation.cs7
-rw-r--r--lib/Net.Http/src/Core/InitDataBuffer.cs10
-rw-r--r--lib/Net.Http/src/Core/PerfCounter/HttpPerfCounterState.cs6
-rw-r--r--lib/Net.Http/src/Core/Request/HttpInputStream.cs34
-rw-r--r--lib/Net.Http/src/Core/Request/HttpRequest.cs42
-rw-r--r--lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs53
-rw-r--r--lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs96
-rw-r--r--lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs14
-rw-r--r--lib/Net.Http/src/Core/Response/HttpResponse.cs44
-rw-r--r--lib/Net.Http/src/Core/Response/ReusableResponseStream.cs46
-rw-r--r--lib/Net.Http/src/Core/TransportManager.cs99
-rw-r--r--lib/Net.Http/src/Core/TransportReader.cs26
-rw-r--r--lib/Net.Http/src/HttpConfig.cs53
23 files changed, 678 insertions, 395 deletions
diff --git a/lib/Net.Http/src/AlternateProtocolBase.cs b/lib/Net.Http/src/AlternateProtocolBase.cs
index e7b9a61..ab9f906 100644
--- a/lib/Net.Http/src/AlternateProtocolBase.cs
+++ b/lib/Net.Http/src/AlternateProtocolBase.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -27,8 +27,6 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
-using VNLib.Net.Http.Core;
-
namespace VNLib.Net.Http
{
/// <summary>
@@ -58,8 +56,9 @@ namespace VNLib.Net.Http
try
{
//Call child initialize method
- await RunAsync(transport);
- CancelSource.Cancel();
+ await RunAsync(transport).ConfigureAwait(false);
+
+ await CancelSource.CancelAsync();
}
finally
{
@@ -73,7 +72,7 @@ namespace VNLib.Net.Http
/// <summary>
/// Is the current socket connected using transport security
/// </summary>
- public virtual bool IsSecure { get; init; }
+ public required virtual bool IsSecure { get; init; }
/// <summary>
/// Determines if the instance is pending cancelation
diff --git a/lib/Net.Http/src/Core/Buffering/ContextLockedBufferManager.cs b/lib/Net.Http/src/Core/Buffering/ContextLockedBufferManager.cs
index 90bdd8c..b11b62b 100644
--- a/lib/Net.Http/src/Core/Buffering/ContextLockedBufferManager.cs
+++ b/lib/Net.Http/src/Core/Buffering/ContextLockedBufferManager.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -33,49 +33,31 @@
using System;
using System.Buffers;
+using System.Diagnostics;
using VNLib.Utils.Memory;
namespace VNLib.Net.Http.Core.Buffering
{
- internal sealed class ContextLockedBufferManager : IHttpBufferManager
+ internal sealed class ContextLockedBufferManager(in HttpBufferConfig config, bool chunkingEnabled) : IHttpBufferManager
{
- private readonly HttpBufferConfig Config;
- private readonly int TotalBufferSize;
+ private readonly HttpBufferConfig Config = config;
+ private readonly bool _chunkingEnabled = chunkingEnabled;
+ private readonly int TotalBufferSize = ComputeTotalBufferSize(in config, chunkingEnabled);
- private readonly HeaderAccumulatorBuffer _requestHeaderBuffer;
- private readonly HeaderAccumulatorBuffer _responseHeaderBuffer;
- private readonly ChunkAccBuffer _chunkAccBuffer;
- private readonly bool _chunkingEnabled;
-
- public ContextLockedBufferManager(in HttpBufferConfig config, bool chunkingEnabled)
- {
- Config = config;
- _chunkingEnabled = chunkingEnabled;
-
- //Compute total buffer size from server config
- TotalBufferSize = ComputeTotalBufferSize(in config, chunkingEnabled);
-
- /*
- * Individual instances of the header accumulator buffer are required
- * because the user controls the size of the binary buffer for responses
- * and requests. The buffer segment is shared between the two instances.
- */
- _requestHeaderBuffer = new(config.RequestHeaderBufferSize);
- _responseHeaderBuffer = new(config.ResponseHeaderBufferSize);
-
- _chunkAccBuffer = new();
- }
+ private readonly HeaderAccumulatorBuffer _requestHeaderBuffer = new(config.RequestHeaderBufferSize);
+ private readonly HeaderAccumulatorBuffer _responseHeaderBuffer = new(config.ResponseHeaderBufferSize);
+ private readonly ChunkAccBuffer _chunkAccBuffer = new();
private IMemoryOwner<byte>? _handle;
private HttpBufferSegments<byte> _segments;
- #region LifeCycle
-
///<inheritdoc/>
public void AllocateBuffer(IHttpMemoryPool allocator)
{
+ Debug.Assert(_handle == null, "Memory Leak: new http buffer alloacted when an existing buffer was not freed.");
+
//Alloc a single buffer for the entire context
_handle = allocator.AllocateBufferForContext(TotalBufferSize);
@@ -84,26 +66,21 @@ namespace VNLib.Net.Http.Core.Buffering
Memory<byte> full = _handle.Memory;
//Header parse buffer is a special case as it will be double the size due to the char buffer
- int headerParseBufferSize = GetMaxHeaderBufferSize(in Config);
-
- //Response/form data buffer
+ int headerParseBufferSize = GetMaxHeaderBufferSize(in Config);
int responseAndFormDataSize = ComputeResponseAndFormDataBuffer(in Config);
- //Slice and store the buffer segments
- _segments = new()
- {
- //Shared header buffer
- HeaderAccumulator = GetNextSegment(ref full, headerParseBufferSize),
+ //Shared header buffer
+ _segments.HeaderAccumulator = GetNextSegment(ref full, headerParseBufferSize);
+ _segments.ResponseAndFormData = GetNextSegment(ref full, responseAndFormDataSize);
- //Shared response and form data buffer
- ResponseAndFormData = GetNextSegment(ref full, responseAndFormDataSize),
-
- /*
- * The chunk accumulator buffer cannot be shared. It is also only
- * stored if chunking is enabled.
- */
- ChunkedResponseAccumulator = _chunkingEnabled ? GetNextSegment(ref full, Config.ChunkedResponseAccumulatorSize) : default
- };
+ /*
+ * The chunk accumulator buffer cannot be shared. It is also only
+ * stored if chunking is enabled.
+ */
+ _segments.ChunkedResponseAccumulator = _chunkingEnabled
+ ? GetNextSegment(ref full, Config.ChunkedResponseAccumulatorSize)
+ : default;
+
/*
* ************* WARNING ****************
@@ -151,8 +128,7 @@ namespace VNLib.Net.Http.Core.Buffering
//Clear segments
_segments = default;
-
- //Free buffer
+
if (_handle != null)
{
_handle.Dispose();
@@ -160,8 +136,6 @@ namespace VNLib.Net.Http.Core.Buffering
}
}
- #endregion
-
///<inheritdoc/>
public IHttpHeaderParseBuffer RequestHeaderParseBuffer => _requestHeaderBuffer;
@@ -171,6 +145,19 @@ namespace VNLib.Net.Http.Core.Buffering
///<inheritdoc/>
public IChunkAccumulatorBuffer ChunkAccumulatorBuffer => _chunkAccBuffer;
+ public Memory<byte> GetInitStreamBuffer()
+ {
+ /*
+ * Since this buffer must be shared with char buffers, size
+ * must be respected. Remember that split buffesr store binary
+ * data at the head of the buffer and char data at the tail
+ */
+
+ Memory<byte> dataBuffer = RequestHeaderParseBuffer.GetMemory();
+
+ return dataBuffer[..RequestHeaderParseBuffer.BinSize];
+ }
+
/*
* Response buffer and form data buffer are shared because they are never
@@ -230,19 +217,17 @@ namespace VNLib.Net.Http.Core.Buffering
}
- readonly struct HttpBufferSegments<T>
+ struct HttpBufferSegments<T>
{
- public readonly Memory<T> HeaderAccumulator { get; init; }
- public readonly Memory<T> ChunkedResponseAccumulator { get; init; }
- public readonly Memory<T> ResponseAndFormData { get; init; }
+ public Memory<T> HeaderAccumulator;
+ public Memory<T> ChunkedResponseAccumulator;
+ public Memory<T> ResponseAndFormData;
}
- private sealed class HeaderAccumulatorBuffer: SplitHttpBufferElement, IResponseHeaderAccBuffer, IHttpHeaderParseBuffer
- {
- public HeaderAccumulatorBuffer(int binSize):base(binSize)
- { }
- }
+ private sealed class HeaderAccumulatorBuffer(int binSize) :
+ SplitHttpBufferElement(binSize), IResponseHeaderAccBuffer, IHttpHeaderParseBuffer
+ { }
private sealed class ChunkAccBuffer : HttpBufferElement, IChunkAccumulatorBuffer
{ }
diff --git a/lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs b/lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs
index 774ed6a..173048a 100644
--- a/lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs
+++ b/lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs
@@ -76,7 +76,9 @@ namespace VNLib.Net.Http.Core.Buffering
///<inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public virtual Span<byte> GetBinSpan(int offset, int size)
- => (offset + size) < _handle.Size ? _handle.GetSpan(offset, size) : throw new ArgumentOutOfRangeException(nameof(offset));
+ => (offset + size) < _handle.Size
+ ? _handle.GetSpan(offset, size)
+ : throw new ArgumentOutOfRangeException(nameof(offset));
private struct HandleState
@@ -87,6 +89,7 @@ namespace VNLib.Net.Http.Core.Buffering
public readonly int Size;
public readonly Memory<byte> Memory;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public HandleState(Memory<byte> mem)
{
Memory = mem;
@@ -97,12 +100,14 @@ namespace VNLib.Net.Http.Core.Buffering
public readonly void Unpin() => _handle.Dispose();
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> GetSpan(int offset, int size)
{
Debug.Assert((offset + size) < Size, "Call to GetSpan failed because the offset/size was out of valid range");
return MemoryUtil.GetSpan<byte>(IntPtr.Add(_pointer, offset), size);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ref byte GetRef() => ref MemoryUtil.GetRef<byte>(_pointer);
}
}
diff --git a/lib/Net.Http/src/Core/Buffering/SplitHttpBufferElement.cs b/lib/Net.Http/src/Core/Buffering/SplitHttpBufferElement.cs
index b6d0d39..19a463c 100644
--- a/lib/Net.Http/src/Core/Buffering/SplitHttpBufferElement.cs
+++ b/lib/Net.Http/src/Core/Buffering/SplitHttpBufferElement.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -30,12 +30,10 @@ using VNLib.Utils.Memory;
namespace VNLib.Net.Http.Core.Buffering
{
- internal abstract class SplitHttpBufferElement : HttpBufferElement, ISplitHttpBuffer
+ internal abstract class SplitHttpBufferElement(int binSize) : HttpBufferElement, ISplitHttpBuffer
{
///<inheritdoc/>
- public int BinSize { get; }
-
- internal SplitHttpBufferElement(int binSize) => BinSize = binSize;
+ public int BinSize { get; } = binSize;
///<inheritdoc/>
public Span<char> GetCharSpan()
diff --git a/lib/Net.Http/src/Core/HttpContext.cs b/lib/Net.Http/src/Core/HttpContext.cs
index f742e97..aa6f260 100644
--- a/lib/Net.Http/src/Core/HttpContext.cs
+++ b/lib/Net.Http/src/Core/HttpContext.cs
@@ -23,8 +23,8 @@
*/
using System;
-using System.IO;
using System.Text;
+using System.Threading;
using System.Diagnostics;
using System.Threading.Tasks;
@@ -80,6 +80,7 @@ namespace VNLib.Net.Http.Core
/// </remarks>
public IAlternateProtocol? AlternateProtocol { get; set; }
+ private readonly TransportManager Transport;
private readonly ManagedHttpCompressor? _compressor;
private ITransportContext? _ctx;
@@ -104,17 +105,16 @@ namespace VNLib.Net.Http.Core
_compressor = null;
}
+ Transport = new();
+
//Init buffer manager, if compression is supported, we need to alloc a buffer for the compressor
Buffers = new(server.Config.BufferConfig, _compressor != null);
-
- //Create new request
- Request = new (this, server.Config.MaxUploadsPerRequest);
-
- //create a new response object
- Response = new (this, Buffers);
-
- //Init response writer
- ResponseBody = new ResponseWriter();
+
+ Request = new (Transport, server.Config.MaxUploadsPerRequest);
+
+ Response = new (this, Transport, Buffers);
+
+ ResponseBody = new ResponseWriter();
}
/// <summary>
@@ -132,13 +132,65 @@ namespace VNLib.Net.Http.Core
HttpVersion IHttpContextInformation.CurrentVersion => Request.State.HttpVersion;
///<inheritdoc/>
- public ref readonly HttpEncodedSegment CrlfSegment => ref ParentServer.CrlfBytes;
+ public ref readonly HttpEncodedSegment CrlfSegment => ref ParentServer.Config.CrlfBytes;
///<inheritdoc/>
- public ref readonly HttpEncodedSegment FinalChunkSegment => ref ParentServer.FinalChunkBytes;
+ public ref readonly HttpEncodedSegment FinalChunkSegment => ref ParentServer.Config.FinalChunkBytes;
+
- ///<inheritdoc/>
- public Stream GetTransport() => _ctx!.ConnectionStream;
+ int _bytesRead;
+
+ /*
+ * The following functions operate in tandem. Data should be buffered
+ * by a call to BufferTransportAsync() and then made availbe by a call to
+ * GetReader(). This set of functions only happens once per request/response
+ * cycle. This allows a async buffer filling before a syncronous transport
+ * read.
+ */
+
+ public void GetReader(out TransportReader reader)
+ {
+ Debug.Assert(_ctx != null, "Request to transport reader was called by the connection context was null");
+
+ reader = new(
+ _ctx!.ConnectionStream,
+ Buffers.RequestHeaderParseBuffer,
+ ParentServer.Config.HttpEncoding,
+ ParentServer.Config.HeaderLineTermination
+ );
+
+ /*
+ * Specal function to set available data
+ * NOTE: this can be dangerous as the buffer is
+ */
+ reader.SetAvailableData(_bytesRead);
+
+ Debug.Assert(reader.Available == _bytesRead);
+ }
+
+ public async ValueTask BufferTransportAsync(CancellationToken cancellation)
+ {
+ /*
+ * This function allows for pre-buffering of the transport
+ * before parsing the request. It also allows waiting for more data async
+ * when an http1 request is in keep-alive mode waiting for more data.
+ *
+ * We can asynchronously read data when its available and preload
+ * the transport reader. The only catch is we need to access the
+ * raw Memory<byte> structure within the buffer. So the binary
+ * buffer size MUST be respected.
+ */
+
+ Debug.Assert(_ctx != null, "Request to buffer transport was called by the connection context was null");
+
+ _bytesRead = 0;
+
+ Memory<byte> dataBuffer = Buffers.GetInitStreamBuffer();
+
+ _bytesRead = await _ctx!.ConnectionStream.ReadAsync(dataBuffer, cancellation);
+
+ Debug.Assert(_bytesRead <= dataBuffer.Length);
+ }
#endregion
@@ -153,7 +205,7 @@ namespace VNLib.Net.Http.Core
Buffers.AllocateBuffer(ParentServer.Config.MemoryPool);
//Init new connection
- Response.OnNewConnection(ctx.ConnectionStream);
+ Transport.OnNewConnection(ctx.ConnectionStream);
}
///<inheritdoc/>
@@ -176,6 +228,8 @@ namespace VNLib.Net.Http.Core
///<inheritdoc/>
public void EndRequest()
{
+ _bytesRead = 0; //Must reset after every request
+
Request.OnComplete();
Response.OnComplete();
ResponseBody.OnComplete();
@@ -191,6 +245,8 @@ namespace VNLib.Net.Http.Core
bool IReusable.Release()
{
+ Transport.OnRelease();
+
_ctx = null;
AlternateProtocol = null;
@@ -206,4 +262,4 @@ namespace VNLib.Net.Http.Core
#endregion
}
-} \ No newline at end of file
+}
diff --git a/lib/Net.Http/src/Core/HttpEncodedSegment.cs b/lib/Net.Http/src/Core/HttpEncodedSegment.cs
index bcb1f3b..cfeb52e 100644
--- a/lib/Net.Http/src/Core/HttpEncodedSegment.cs
+++ b/lib/Net.Http/src/Core/HttpEncodedSegment.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -51,12 +51,15 @@ namespace VNLib.Net.Http.Core
/// Performs a dangerous reference based copy-to (aka memmove)
/// </summary>
/// <param name="output">The output buffer to write the encoded segment to</param>
- internal readonly int DangerousCopyTo(Span<byte> output)
+ /// <param name="offset">Points to the first byte in the buffer to write to</param>
+ internal readonly int DangerousCopyTo(Span<byte> output, int offset)
{
Debug.Assert(output.Length >= Length, "Output span was empty and could not be written to");
+ Debug.Assert(offset >= 0, "Buffer underrun detected");
+ Debug.Assert(offset + Length <= output.Length, "Output span was too small to hold the encoded segment");
//Get reference of output buffer span
- return DangerousCopyTo(ref MemoryMarshal.GetReference(output));
+ return DangerousCopyTo(ref MemoryMarshal.GetReference(output), (nuint)offset);
}
/// <summary>
@@ -73,14 +76,15 @@ namespace VNLib.Net.Http.Core
//Ensure enough space is available
if(offset + Length <= buffer.Size)
{
+ //More efficient to get the offset ref from the buffer directly
ref byte dst = ref buffer.DangerousGetBinRef(offset);
- return DangerousCopyTo(ref dst);
+ return DangerousCopyTo(ref dst, destOffset: 0);
}
throw new ArgumentOutOfRangeException(nameof(offset), "Buffer is too small to hold the encoded segment");
}
- private readonly int DangerousCopyTo(ref byte output)
+ private readonly int DangerousCopyTo(ref byte output, nuint destOffset)
{
Debug.Assert(!Unsafe.IsNullRef(ref output), "Output span was empty and could not be written to");
@@ -88,7 +92,7 @@ namespace VNLib.Net.Http.Core
ref byte src = ref MemoryMarshal.GetArrayDataReference(Buffer);
//Call memmove with the buffer offset and desired length
- MemoryUtil.SmallMemmove(ref src, Offset, ref output, 0, Length);
+ MemoryUtil.SmallMemmove(ref src, Offset, ref output, destOffset, Length);
return Length;
}
diff --git a/lib/Net.Http/src/Core/HttpEvent.cs b/lib/Net.Http/src/Core/HttpEvent.cs
index 37c5ab5..270e7f8 100644
--- a/lib/Net.Http/src/Core/HttpEvent.cs
+++ b/lib/Net.Http/src/Core/HttpEvent.cs
@@ -91,11 +91,11 @@ namespace VNLib.Net.Http
//If stream is empty, ignore it, the server will default to 0 content length and avoid overhead
if (length == 0)
{
+ //Stream is disposed because it is assumed we now own the lifecycle of the stream
stream.Dispose();
return;
}
-
- //Set status code
+
Context.Response.SetStatusCode(code);
//Finally store the stream input
@@ -120,11 +120,11 @@ namespace VNLib.Net.Http
//If stream is empty, ignore it, the server will default to 0 content length and avoid overhead
if (entity.Remaining == 0)
{
+ //Stream is disposed because it is assumed we now own the lifecycle of the stream
entity.Close();
return;
}
-
- //Set status code
+
Context.Response.SetStatusCode(code);
//Store the memory reader input
@@ -150,6 +150,7 @@ namespace VNLib.Net.Http
//If stream is empty, ignore it, the server will default to 0 content length and avoid overhead
if (length == 0)
{
+ //Stream is disposed because it is assumed we now own the lifecycle of the stream
stream.Dispose();
return;
}
diff --git a/lib/Net.Http/src/Core/HttpServerBase.cs b/lib/Net.Http/src/Core/HttpServerBase.cs
index f5f3563..b4fd17d 100644
--- a/lib/Net.Http/src/Core/HttpServerBase.cs
+++ b/lib/Net.Http/src/Core/HttpServerBase.cs
@@ -35,7 +35,23 @@
* to function safely with async programming practices.
*/
+/*
+ * 6-26-2024
+ *
+ * Server has been transformed to ultilize 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;
using System.Net.Sockets;
using System.Threading.Tasks;
@@ -74,9 +90,8 @@ namespace VNLib.Net.Http
/// </summary>
internal static readonly Memory<byte> WriteOnlyScratchBuffer = new byte[64 * 1024];
- private readonly ITransportProvider Transport;
- private readonly FrozenDictionary<string, IWebRoot> ServerRoots;
- private readonly IWebRoot? _wildcardRoot;
+ private readonly ListenerState[] Transports;
+
private readonly HttpConfig _config;
#region caches
@@ -89,11 +104,7 @@ namespace VNLib.Net.Http
/// Reusable store for obtaining <see cref="HttpContext"/>
/// </summary>
private readonly ObjectRental<HttpContext> ContextStore;
-
- /// <summary>
- /// The cached header-line termination value
- /// </summary>
- private readonly ReadOnlyMemory<byte> HeaderLineTermination;
+
#endregion
/// <summary>
@@ -104,23 +115,13 @@ namespace VNLib.Net.Http
/// <summary>
/// Gets a value indicating whether the server is listening for connections
/// </summary>
- public bool Running { get; private set; }
+ public bool Running => Transports.Any(static t => t.Running);
/// <summary>
/// Cached supported compression methods
/// </summary>
internal readonly CompressionMethod SupportedCompressionMethods;
- /// <summary>
- /// Pre-encoded CRLF bytes
- /// </summary>
- internal readonly HttpEncodedSegment CrlfBytes;
-
- /// <summary>
- /// Pre-encoded HTTP chunking final chunk segment
- /// </summary>
- internal readonly HttpEncodedSegment FinalChunkBytes;
-
private CancellationTokenSource? StopToken;
/// <summary>
@@ -128,38 +129,28 @@ namespace VNLib.Net.Http
/// Immutable data structures are initialzed.
/// </summary>
/// <param name="config">The configuration used to create the instance</param>
- /// <param name="transport">The transport provider to listen to connections from</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, ITransportProvider transport, 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}";
- //Setup config copy with the internal http pool
- Transport = transport;
+ KeepAliveTimeoutHeaderValue = $"timeout={(int)_config.ConnectionKeepAlive.TotalSeconds}";
+
+ //Map transport listeners to their virtual hosts
+ Transports = MapListeners(bindings.ToArray());
+
//Cache supported compression methods, or none if compressor is null
- SupportedCompressionMethods = config.CompressorManager == null ?
- CompressionMethod.None :
- config.CompressorManager.GetSupportedMethods();
+ SupportedCompressionMethods = config.CompressorManager == null
+ ? CompressionMethod.None
+ : config.CompressorManager.GetSupportedMethods();
//Create a new context store
ContextStore = ObjectRental.CreateReusable(() => new HttpContext(this, SupportedCompressionMethods));
-
- //Cache wildcard root
- _wildcardRoot = ServerRoots.GetValueOrDefault(WILDCARD_KEY);
-
- //Init pre-encded segments
- CrlfBytes = HttpEncodedSegment.FromString(HttpHelpers.CRLF, config.HttpEncoding);
- FinalChunkBytes = HttpEncodedSegment.FromString("0\r\n\r\n", config.HttpEncoding);
-
- //Store a ref to the crlf memory segment
- HeaderLineTermination = CrlfBytes.Buffer.AsMemory();
}
private static void ValidateConfig(in HttpConfig conf)
@@ -251,6 +242,34 @@ namespace VNLib.Net.Http
}
}
+ private static ListenerState[] MapListeners(HttpTransportBinding[] bindings)
+ {
+ if(bindings.Any(static b => b is null))
+ {
+ throw new ArgumentNullException(nameof(bindings),"Transport bindings containing a null entry.");
+ }
+
+ /*
+ * 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>
@@ -263,32 +282,50 @@ namespace VNLib.Net.Http
public Task Start(CancellationToken cancellationToken)
{
StopToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
- //Start servers with the new token source
- Transport.Start(StopToken.Token);
- //Start the listen task
- return Task.Run(ListenWorkerDoWork, cancellationToken);
+
+ //Start servers with the new token source before listening for connections
+ Array.ForEach(Transports, p => p.OriginServer.Start(StopToken.Token));
+
+ //Listen to connections on all transports async
+ IEnumerable<Task> runTasks = Transports.Select(ListenAsync);
+
+ //Calling WhenAll() will force the numeration and schedule listening tasks
+ return Task.WhenAll(runTasks);
+
+ //Defer listening tasks to the task scheduler to avoid blocking this thread
+ Task ListenAsync(ListenerState tp) => Task.Run(() => ListenWorkerDoWork(tp), cancellationToken);
}
/*
* A worker task that listens for connections from the transport
*/
- private async Task ListenWorkerDoWork()
+ private async Task ListenWorkerDoWork(ListenerState state)
{
- //Set running flag
- Running = true;
-
- _config.ServerLog.Information("HTTP server {hc} listening for connections", GetHashCode());
+ state.Running = true;
+
+ if (_config.ServerLog.IsEnabled(LogLevel.Verbose))
+ {
+ _config.ServerLog.Verbose(
+ format: "HTTP server {hc} listening for connections on {iface}",
+ GetHashCode(),
+ state.OriginServer
+ );
+ }
+ else
+ {
+ _config.ServerLog.Information("HTTP server {hc} listening for connections", GetHashCode());
+ }
//Listen for connections until canceled
- while (true)
+ do
{
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)
{
@@ -299,13 +336,14 @@ namespace VNLib.Net.Http
{
_config.ServerLog.Error(ex);
}
- }
+
+ } while (true);
//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());
}
@@ -341,5 +379,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 b6dbfef..8594ea0 100644
--- a/lib/Net.Http/src/Core/HttpServerProcessing.cs
+++ b/lib/Net.Http/src/Core/HttpServerProcessing.cs
@@ -29,14 +29,12 @@ using System.Threading;
using System.Net.Sockets;
using System.Diagnostics;
using System.Threading.Tasks;
-using System.Collections.Generic;
using System.Runtime.CompilerServices;
using VNLib.Utils.Memory;
using VNLib.Utils.Logging;
using VNLib.Utils.Extensions;
using VNLib.Net.Http.Core;
-using VNLib.Net.Http.Core.Buffering;
using VNLib.Net.Http.Core.Response;
using VNLib.Net.Http.Core.PerfCounter;
@@ -48,32 +46,37 @@ 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)
{
- //Increment open connection count
Interlocked.Increment(ref OpenConnectionCount);
-
- //Rent a new context object to reuse
+
HttpContext? context = ContextStore.Rent();
try
{
Stream stream = transportContext.ConnectionStream;
- //Set write timeout
+ /*
+ * Write timeout is constant for the duration of an HTTP
+ * connection. Read timeout must be set to active on initial
+ * loop because a fresh connection is assumed to have data
+ * ready.
+ */
stream.WriteTimeout = _config.SendTimeout;
+ stream.ReadTimeout = _config.ActiveConnectionRecvTimeout;
- //Init stream
context.InitializeContext(transportContext);
- //Keep the transport open and listen for messages as long as keepalive is enabled
+ //Keepalive loop
do
{
- //Set rx timeout low for initial reading
+ //Attempt to buffer a new (or keepalive) connection async
+ await context.BufferTransportAsync(StopToken!.Token);
+
+ //Return read timeout to active connection timeout after data is received
stream.ReadTimeout = _config.ActiveConnectionRecvTimeout;
- //Process the request
- bool keepAlive = await ProcessHttpEventAsync(context);
+ bool keepAlive = await ProcessHttpEventAsync(listenState, context);
//If not keepalive, exit the listening loop and clean up connection
if (!keepAlive)
@@ -81,15 +84,17 @@ namespace VNLib.Net.Http
break;
}
- //Reset inactive keeaplive timeout, when expired the following read will throw a cancealltion exception
+ //Timeout reset to keepalive timeout waiting for more data on the transport
stream.ReadTimeout = (int)_config.ConnectionKeepAlive.TotalMilliseconds;
- //"Peek" or wait for more data to begin another request (may throw timeout exception when timmed out)
- await stream.ReadAsync(Memory<byte>.Empty, StopToken!.Token);
-
} while (true);
- //Check if an alternate protocol was specified
+ /*
+ * If keepalive loop breaks, its possible that the connection
+ * wishes to upgrade to an alternate protocol.
+ *
+ * Process it here to allow freeing context related resources
+ */
if (context.AlternateProtocol != null)
{
//Save the current ap
@@ -139,18 +144,16 @@ namespace VNLib.Net.Http
{
_config.ServerLog.Error(ex);
}
-
- //Dec open connection count
+
Interlocked.Decrement(ref OpenConnectionCount);
//Return the context for normal operation (alternate protocol will return before now so it will be null)
if(context != null)
{
- //Return context to store
ContextStore.Return(context);
}
-
- //Close the transport async
+
+ //All done, time to close transport and exit
try
{
await transportContext.CloseConnectionAsync();
@@ -165,9 +168,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;
@@ -196,9 +200,8 @@ namespace VNLib.Net.Http
{
return false;
}
-
- //process the request
- bool processSuccess = await ProcessRequestAsync(context);
+
+ bool processSuccess = await ProcessRequestAsync(listenState, context);
#if DEBUG
static void WriteConnectionDebugLog(HttpServer server, HttpContext context)
@@ -276,17 +279,14 @@ namespace VNLib.Net.Http
//TODO: future support for http2 and http3 over tls
}
- //Get the parse buffer
- IHttpHeaderParseBuffer parseBuffer = ctx.Buffers.RequestHeaderParseBuffer;
-
- TransportReader reader = new (ctx.GetTransport(), parseBuffer, _config.HttpEncoding, HeaderLineTermination);
+ ctx.GetReader(out TransportReader reader);
HttpStatusCode code;
try
{
//Get the char span
- Span<char> lineBuf = parseBuffer.GetCharSpan();
+ Span<char> lineBuf = ctx.Buffers.RequestHeaderParseBuffer.GetCharSpan();
Http11ParseExtensions.Http1ParseState parseState = new();
@@ -294,14 +294,12 @@ namespace VNLib.Net.Http
{
return code;
}
-
- //Parse the headers
+
if ((code = ctx.Request.Http1ParseHeaders(ref parseState, ref reader, in _config, lineBuf)) > 0)
{
return code;
}
-
- //Prepare entity body for request
+
if ((code = ctx.Request.Http1PrepareEntityBody(ref parseState, ref reader, in _config)) > 0)
{
return code;
@@ -384,16 +382,20 @@ 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);
-
- if (root == null)
+
+ if (!listenState.Roots.TryGetValue(context.Request.State.Location.DnsSafeHost, out IWebRoot? root))
{
- context.Respond(HttpStatusCode.NotFound);
- //make sure control leaves
- return true;
+ if (listenState.DefaultRoute is null)
+ {
+ context.Respond(HttpStatusCode.NotFound);
+ //make sure control leaves
+ return true;
+ }
+
+ root = listenState.DefaultRoute;
}
//Check the expect header and return an early status code
@@ -476,4 +478,4 @@ namespace VNLib.Net.Http
}
}
-} \ No newline at end of file
+}
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/IHttpContextInformation.cs b/lib/Net.Http/src/Core/IHttpContextInformation.cs
index 38e86b3..cb51e9f 100644
--- a/lib/Net.Http/src/Core/IHttpContextInformation.cs
+++ b/lib/Net.Http/src/Core/IHttpContextInformation.cs
@@ -22,7 +22,6 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
-using System.IO;
using System.Text;
namespace VNLib.Net.Http.Core
@@ -48,11 +47,5 @@ namespace VNLib.Net.Http.Core
/// The current connection's http version
/// </summary>
HttpVersion CurrentVersion { get; }
-
- /// <summary>
- /// Gets the transport stream for the current connection.
- /// </summary>
- /// <returns>The current transport stream</returns>
- Stream GetTransport();
}
} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/InitDataBuffer.cs b/lib/Net.Http/src/Core/InitDataBuffer.cs
index 6d559cd..d66b34a 100644
--- a/lib/Net.Http/src/Core/InitDataBuffer.cs
+++ b/lib/Net.Http/src/Core/InitDataBuffer.cs
@@ -128,11 +128,11 @@ namespace VNLib.Net.Http.Core
int bytesToRead = Math.Min(Remaining, buffer.Length);
MemoryUtil.Memmove(
- ref MemoryMarshal.GetArrayDataReference(_buffer),
- (nuint)GetDataPosition(),
- ref MemoryMarshal.GetReference(buffer),
- 0,
- (nuint)bytesToRead
+ src: in MemoryMarshal.GetArrayDataReference(_buffer),
+ srcOffset: (nuint)GetDataPosition(),
+ dst: ref MemoryMarshal.GetReference(buffer),
+ dstOffset: 0,
+ elementCount: (nuint)bytesToRead
);
//Update position pointer
diff --git a/lib/Net.Http/src/Core/PerfCounter/HttpPerfCounterState.cs b/lib/Net.Http/src/Core/PerfCounter/HttpPerfCounterState.cs
index a86ac40..89b622d 100644
--- a/lib/Net.Http/src/Core/PerfCounter/HttpPerfCounterState.cs
+++ b/lib/Net.Http/src/Core/PerfCounter/HttpPerfCounterState.cs
@@ -3,10 +3,10 @@
*
* Library: VNLib
* Package: VNLib.Net.Http
-* File: ConnectionInfo.cs
+* File: HttpPerfCounterState.cs
*
-* ConnectionInfo.cs is part of VNLib.Net.Http which is part of the larger
-* VNLib collection of libraries and utilities.
+* HttpPerfCounterState.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
diff --git a/lib/Net.Http/src/Core/Request/HttpInputStream.cs b/lib/Net.Http/src/Core/Request/HttpInputStream.cs
index ccaa336..29dda2d 100644
--- a/lib/Net.Http/src/Core/Request/HttpInputStream.cs
+++ b/lib/Net.Http/src/Core/Request/HttpInputStream.cs
@@ -38,7 +38,7 @@ namespace VNLib.Net.Http.Core
/// <summary>
/// Specialized stream to allow reading a request entity body with a fixed content length.
/// </summary>
- internal sealed class HttpInputStream(IHttpContextInformation ContextInfo) : Stream
+ internal sealed class HttpInputStream(TransportManager transport) : Stream
{
private StreamState _state;
private InitDataBuffer? _initalData;
@@ -74,7 +74,6 @@ namespace VNLib.Net.Http.Core
internal ref InitDataBuffer? Prepare(long contentLength)
{
_state.ContentLength = contentLength;
- _state.InputStream = ContextInfo.GetTransport();
return ref _initalData;
}
@@ -153,10 +152,8 @@ namespace VNLib.Net.Http.Core
//See if data is still remaining to be read from transport (reamining size is also the amount of data that can be read)
if (writer.RemainingSize > 0)
{
- //Read from transport
- ERRNO read = _state.InputStream!.Read(writer.Remaining);
+ ERRNO read = transport.Stream!.Read(writer.Remaining);
- //Update writer position
writer.Advance(read);
_state.Position += read;
@@ -189,27 +186,23 @@ namespace VNLib.Net.Http.Core
{
//Read as much as possible from internal buffer
ERRNO read = _initalData.Value.Read(writer.Remaining.Span);
-
- //Advance writer
+
writer.Advance(read);
-
- //Update position
+
_state.Position += read;
}
//See if data is still remaining to be read from transport (reamining size is also the amount of data that can be read)
if (writer.RemainingSize > 0)
- {
- //Read from transport
- int read = await _state.InputStream!.ReadAsync(writer.Remaining, cancellationToken).ConfigureAwait(true);
-
- //Update writer position
+ {
+ int read = await transport.Stream.ReadAsync(writer.Remaining, cancellationToken)
+ .ConfigureAwait(true);
+
writer.Advance(read);
_state.Position += read;
}
-
- //Return number of bytes written to the buffer
+
return writer.Written;
}
@@ -249,13 +242,11 @@ namespace VNLib.Net.Http.Core
while (bytesToRead > 0)
{
//Read data to the discard buffer until reading is completed (read == 0)
- read = await _state.InputStream!.ReadAsync(HttpServer.WriteOnlyScratchBuffer.Slice(0, bytesToRead), CancellationToken.None)
+ read = await transport.Stream!.ReadAsync(HttpServer.WriteOnlyScratchBuffer.Slice(0, bytesToRead), CancellationToken.None)
.ConfigureAwait(true);
-
- //Update position
+
_state.Position += read;
-
- //Recalculate the number of bytes to read
+
bytesToRead = (int)Math.Min(HttpServer.WriteOnlyScratchBuffer.Length, Remaining);
}
}
@@ -286,7 +277,6 @@ namespace VNLib.Net.Http.Core
private struct StreamState
{
- public Stream? InputStream;
public long Position;
public long ContentLength;
}
diff --git a/lib/Net.Http/src/Core/Request/HttpRequest.cs b/lib/Net.Http/src/Core/Request/HttpRequest.cs
index 2c9eed0..cbe6bc0 100644
--- a/lib/Net.Http/src/Core/Request/HttpRequest.cs
+++ b/lib/Net.Http/src/Core/Request/HttpRequest.cs
@@ -32,7 +32,7 @@ using VNLib.Utils.Extensions;
namespace VNLib.Net.Http.Core
{
- internal sealed class HttpRequest(IHttpContextInformation contextInfo, ushort maxUploads) : IHttpLifeCycle
+ internal sealed class HttpRequest(TransportManager transport, ushort maxUploads) : IHttpLifeCycle
#if DEBUG
,IStringSerializeable
#endif
@@ -48,7 +48,7 @@ namespace VNLib.Net.Http.Core
/// A transport stream wrapper that is positioned for reading
/// the entity body from the input stream
/// </summary>
- public readonly HttpInputStream InputStream = new(contextInfo);
+ public readonly HttpInputStream InputStream = new(transport);
/*
* Evil mutable structure that stores the http request state.
@@ -63,6 +63,7 @@ namespace VNLib.Net.Http.Core
*/
private HttpRequestState _state;
private readonly FileUpload[] _uploads = new FileUpload[maxUploads];
+ private readonly FileUpload[] _singleUpload = new FileUpload[1];
/// <summary>
/// Gets a mutable structure ref only used to initalize the request
@@ -120,13 +121,15 @@ namespace VNLib.Net.Http.Core
}
private void FreeUploadBuffers()
- {
- //Dispose all initialized files
+ {
+ //Dispose all initialized files, should be much faster than using Array.Clear();
for (int i = 0; i < _uploads.Length; i++)
{
_uploads[i].Free();
_uploads[i] = default;
}
+
+ _singleUpload[0] = default;
}
/// <summary>
@@ -136,22 +139,32 @@ namespace VNLib.Net.Http.Core
public bool CanAddUpload() => _state.UploadCount < _uploads.Length;
/// <summary>
- /// Attempts to add a file upload to the request if there
- /// is room for it. If there is no room, it will be ignored.
- /// See <see cref="CanAddUpload"/> to check if another upload can be added.
+ /// Attempts to obtain a reference to the next available
+ /// file upload in the request. If there are no more uploads
+ /// available, a null reference is returned.
/// </summary>
- /// <param name="upload">The file upload structure to add to the list</param>
- public void AddFileUpload(in FileUpload upload)
+ /// <returns>A reference within the upload array to add the file</returns>
+ public ref FileUpload AddFileUpload()
{
//See if there is room for another upload
if (CanAddUpload())
{
- //Add file to upload array and increment upload count
- _uploads[_state.UploadCount++] = upload;
+ //get ref to current position and increment the upload count
+ return ref _uploads[_state.UploadCount++];
}
+
+ return ref Unsafe.NullRef<FileUpload>();
}
/// <summary>
+ /// Attempts to add a file upload to the request if there
+ /// is room for it. If there is no room, it will be ignored.
+ /// See <see cref="CanAddUpload"/> to check if another upload can be added.
+ /// </summary>
+ /// <param name="upload">The file upload structure to add to the list</param>
+ public void AddFileUpload(in FileUpload upload) => AddFileUpload() = upload;
+
+ /// <summary>
/// Creates a new array and copies the uploads to it.
/// </summary>
/// <returns>The array clone of the file uploads</returns>
@@ -162,6 +175,13 @@ namespace VNLib.Net.Http.Core
return Array.Empty<FileUpload>();
}
+ //Shortcut for a single upload request (hotpath optimization)
+ if (_state.UploadCount == 1)
+ {
+ _singleUpload[0] = _uploads[0];
+ return _singleUpload;
+ }
+
//Create new array to hold uploads
FileUpload[] uploads = GC.AllocateUninitializedArray<FileUpload>(_state.UploadCount, false);
diff --git a/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs b/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
index 878622e..64e08c5 100644
--- a/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
+++ b/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
@@ -184,7 +184,7 @@ namespace VNLib.Net.Http.Core
//Default case is store as a file
default:
//add upload (if it fails thats fine, no memory to clean up)
- request.AddFileUpload(new(request.InputStream, false, request.State.ContentType, null));
+ request.AddFileUpload(new(request.InputStream, DisposeStream: false, request.State.ContentType, FileName: null));
break;
}
@@ -222,7 +222,7 @@ namespace VNLib.Net.Http.Core
Encoding encoding
)
{
- int length = 0;
+ int charsRead = 0;
do
{
//read async
@@ -238,18 +238,18 @@ namespace VNLib.Net.Http.Core
int numChars = encoding.GetCharCount(binBuffer.Span[..read]);
//Re-alloc buffer and guard for overflow
- charBuffer.ResizeIfSmaller(checked(numChars + length));
-
- //Decode and update position
- _ = encoding.GetChars(binBuffer.Span[..read], charBuffer.Span.Slice(length, numChars));
-
- //Update char count
- length += numChars;
+ charBuffer.ResizeIfSmaller(checked(numChars + charsRead));
+
+ _ = encoding.GetChars(
+ bytes: binBuffer.Span[..read],
+ chars: charBuffer.Span.Slice(charsRead, numChars)
+ );
+
+ charsRead += numChars;
} while (true);
-
- //Return the number of characters read
- return length;
+
+ return charsRead;
}
/*
@@ -299,8 +299,9 @@ namespace VNLib.Net.Http.Core
switch (headerType)
{
case HttpHelpers.ContentDisposition:
- //Parse the content dispostion
+
HttpHelpers.ParseDisposition(headerValue, out DispType, out Name, out FileName);
+
break;
case HttpRequestHeader.ContentType:
//The header value for content type should be an MIME content type
@@ -320,12 +321,13 @@ namespace VNLib.Net.Http.Core
//Only add the upload if the request can accept more uploads, otherwise drop it
if (state.Request.CanAddUpload())
{
- ReadOnlySpan<char> fileData = reader.Window.TrimCRLF();
-
- FileUpload upload = UploadFromString(fileData, state, FileName, ctHeaderVal);
-
- //Store the file in the uploads
- state.Request.AddFileUpload(in upload);
+ UploadFromString(
+ data: reader.Window.TrimCRLF(),
+ context: state,
+ filename: FileName,
+ contentType: ctHeaderVal,
+ upload: ref state.Request.AddFileUpload()
+ );
}
}
@@ -344,9 +346,16 @@ namespace VNLib.Net.Http.Core
/// <param name="data">The string data to copy</param>
/// <param name="context">The connection context</param>
/// <param name="filename">The name of the file</param>
- /// <param name="ct">The content type of the file data</param>
+ /// <param name="contentType">The content type of the file data</param>
+ /// <param name="upload">A reference to the file upload to assign</param>
/// <returns>The <see cref="FileUpload"/> container</returns>
- private static FileUpload UploadFromString(ReadOnlySpan<char> data, HttpContext context, string filename, ContentType ct)
+ private static void UploadFromString(
+ ReadOnlySpan<char> data,
+ HttpContext context,
+ string filename,
+ ContentType contentType,
+ ref FileUpload upload
+ )
{
IHttpContextInformation info = context;
IHttpMemoryPool pool = context.ParentServer.Config.MemoryPool;
@@ -365,7 +374,7 @@ namespace VNLib.Net.Http.Core
VnMemoryStream vms = VnMemoryStream.FromHandle(buffHandle, true, bytes, true);
//Create new upload wrapper that owns the stream
- return new(vms, true, ct, filename);
+ upload = new(vms, true, contentType, filename);
}
catch
{
diff --git a/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs b/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
index cabb723..dcbd4af 100644
--- a/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
+++ b/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
@@ -49,11 +49,26 @@ namespace VNLib.Net.Http.Core
/// </summary>
public ref struct Http1ParseState
{
- internal UriBuilder? Location;
- internal bool IsAbsoluteRequestUrl;
+ internal Uri? AbsoluteUri;
+ internal UriSegments Location;
internal long ContentLength;
}
+ /*
+ * Reduces load when parsing uri components
+ * and allows a one-time vaidation once the uri
+ * is compiled
+ */
+ internal ref struct UriSegments
+ {
+ public string Scheme;
+ public string Host;
+ public string Path;
+ public string Query;
+
+ public int Port;
+ }
+
/// <summary>
/// Reads the first line from the transport stream using the specified buffer
@@ -120,29 +135,23 @@ namespace VNLib.Net.Http.Core
return HttpStatusCode.HttpVersionNotSupported;
}
- //Set keepalive flag if http11
+ //Http 1.1 spec defaults to keepalive if the connection header is not set to close
reqState.KeepAlive = reqState.HttpVersion == HttpVersion.Http11;
- //Get the location segment from the request line
pathAndQuery = requestLine[(index + 1)..endloc].TrimCRLF();
//Process an absolute uri,
if (pathAndQuery.Contains("://", StringComparison.Ordinal))
{
//Convert the location string to a .net string and init the location builder (will perform validation when the Uri propery is used)
- parseState.Location = new(pathAndQuery.ToString());
- parseState.IsAbsoluteRequestUrl = true;
+ parseState.AbsoluteUri = new(pathAndQuery.ToString());
return 0;
}
- //Try to capture a realative uri
+ //Must be a relaive uri that starts with /
else if (pathAndQuery.Length > 0 && pathAndQuery[0] == '/')
{
- //Create a default location uribuilder
- parseState.Location = new()
- {
- //Set a default scheme
- Scheme = usingTls ? Uri.UriSchemeHttps : Uri.UriSchemeHttp,
- };
+ //Set default scheme
+ parseState.Location.Scheme = usingTls ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
//Need to manually parse the query string
int q = pathAndQuery.IndexOf('?');
@@ -161,6 +170,7 @@ namespace VNLib.Net.Http.Core
}
return 0;
}
+
//Cannot service an unknonw location
return HttpStatusCode.BadRequest;
}
@@ -334,16 +344,20 @@ namespace VNLib.Net.Http.Core
}
//Verify that the host matches the host header if absolue uri is set
- if (parseState.IsAbsoluteRequestUrl)
+ if (parseState.AbsoluteUri != null)
{
- if (!hostOnly.Equals(parseState.Location!.Host, StringComparison.OrdinalIgnoreCase))
+ if (!hostOnly.Equals(parseState.Location.Host, StringComparison.OrdinalIgnoreCase))
{
return HttpStatusCode.BadRequest;
}
}
- //store the host value
- parseState.Location!.Host = hostOnly;
+ /*
+ * Uri segments are only assigned/used if an absolute
+ * uri was not set in the request line.
+ */
+
+ parseState.Location.Host = hostOnly;
//If the port span is empty, no colon was found or the port is invalid
if (!port.IsEmpty)
@@ -444,16 +458,14 @@ namespace VNLib.Net.Http.Core
//Validate explicit range
if(!HttpRange.IsValidRangeValue(startRangeValue, endRangeValue))
{
- //Ignore and continue parsing headers
+ //If range is invalid were supposed to ignore it and continue
break;
}
-
- //Set full http range
+
reqState.Range = HttpRange.FullRange(startRangeValue, endRangeValue);
}
else
{
- //From-end range
reqState.Range = HttpRange.FromEnd(endRangeValue);
}
}
@@ -509,18 +521,43 @@ namespace VNLib.Net.Http.Core
return HttpStatusCode.BadRequest;
}
+ //Store absolute uri if set
+ if(parseState.AbsoluteUri != null)
+ {
+ reqState.Location = parseState.AbsoluteUri;
+ }
//Check the final location to make sure data was properly sent
- if (string.IsNullOrWhiteSpace(parseState.Location?.Host)
+ else if(string.IsNullOrWhiteSpace(parseState.Location.Host)
|| string.IsNullOrWhiteSpace(parseState.Location.Scheme)
|| string.IsNullOrWhiteSpace(parseState.Location.Path)
- )
+ )
{
return HttpStatusCode.BadRequest;
}
+ else
+ {
+ /*
+ * Double allocations are not ideal here, but for now, its the
+ * safest way to build and validate a foreign uri. Its better
+ * than it was.
+ *
+ * A string could be build from heap memory then passed to the
+ * uri constructor for validation, but this will work for now.
+ */
+
+ //Build the final uri if successfully parsed into segments
+ reqState.Location = new UriBuilder(
+ scheme: parseState.Location.Scheme,
+ host: parseState.Location.Host,
+ port: parseState.Location.Port,
+ path: parseState.Location.Path,
+ extraValue: null
+ )
+ {
+ Query = parseState.Location.Query,
+ }.Uri;
+ }
- //Store the finalized location
- reqState.Location = parseState.Location.Uri;
-
return 0;
}
@@ -533,7 +570,12 @@ namespace VNLib.Net.Http.Core
/// <param name="reader">The <see cref="VnStreamReader"/> to read lines from the transport</param>
/// <returns>0 if the request line was successfully parsed, a status code if the request could not be processed</returns>
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
- public static HttpStatusCode Http1PrepareEntityBody(this HttpRequest Request, ref Http1ParseState parseState, ref TransportReader reader, ref readonly HttpConfig Config)
+ public static HttpStatusCode Http1PrepareEntityBody(
+ this HttpRequest Request,
+ ref Http1ParseState parseState,
+ ref TransportReader reader,
+ ref readonly HttpConfig Config
+ )
{
/*
* Evil mutable struct, get a local mutable reference to the request's
diff --git a/lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs b/lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs
index 0611095..a28ce09 100644
--- a/lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs
+++ b/lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -35,16 +35,10 @@ namespace VNLib.Net.Http.Core.Response
/// <summary>
/// Specialized data accumulator for compiling response headers
/// </summary>
- internal readonly struct HeaderDataAccumulator
+ internal readonly struct HeaderDataAccumulator(IResponseHeaderAccBuffer accBuffer, IHttpContextInformation ctx)
{
- private readonly IResponseHeaderAccBuffer _buffer;
- private readonly IHttpContextInformation _contextInfo;
-
- public HeaderDataAccumulator(IResponseHeaderAccBuffer accBuffer, IHttpContextInformation ctx)
- {
- _buffer = accBuffer;
- _contextInfo = ctx;
- }
+ private readonly IResponseHeaderAccBuffer _buffer = accBuffer;
+ private readonly IHttpContextInformation _contextInfo = ctx;
/// <summary>
/// Gets the accumulated response data as its memory buffer, and resets the internal accumulator
diff --git a/lib/Net.Http/src/Core/Response/HttpResponse.cs b/lib/Net.Http/src/Core/Response/HttpResponse.cs
index e354998..a038c9b 100644
--- a/lib/Net.Http/src/Core/Response/HttpResponse.cs
+++ b/lib/Net.Http/src/Core/Response/HttpResponse.cs
@@ -23,7 +23,6 @@
*/
using System;
-using System.IO;
using System.Net;
using System.Threading;
using System.Diagnostics;
@@ -39,7 +38,8 @@ using VNLib.Net.Http.Core.Buffering;
namespace VNLib.Net.Http.Core.Response
{
- internal sealed class HttpResponse(IHttpContextInformation ContextInfo, IHttpBufferManager manager) : IHttpLifeCycle
+
+ internal sealed class HttpResponse(IHttpContextInformation ContextInfo, TransportManager transport, IHttpBufferManager manager) : IHttpLifeCycle
#if DEBUG
, IStringSerializeable
#endif
@@ -47,8 +47,8 @@ namespace VNLib.Net.Http.Core.Response
const int DefaultCookieCapacity = 2;
private readonly Dictionary<string, HttpResponseCookie> Cookies = new(DefaultCookieCapacity, StringComparer.OrdinalIgnoreCase);
- private readonly DirectStream ReusableDirectStream = new();
- private readonly ChunkedStream ReusableChunkedStream = new(manager.ChunkAccumulatorBuffer, ContextInfo);
+ private readonly DirectStream ReusableDirectStream = new(transport);
+ private readonly ChunkedStream ReusableChunkedStream = new(manager.ChunkAccumulatorBuffer, transport, ContextInfo);
private readonly HeaderDataAccumulator Writer = new(manager.ResponseHeaderBuffer, ContextInfo);
private int _headerWriterPosition;
@@ -165,9 +165,6 @@ namespace VNLib.Net.Http.Core.Response
//Update sent headers
HeadersSent = true;
- //Get the transport stream to write the response data to
- Stream transport = ContextInfo.GetTransport();
-
/*
* ASYNC NOTICE: It is safe to get the memory block then return the task
* because the response writer is not cleaned up until the OnComplete()
@@ -175,7 +172,7 @@ namespace VNLib.Net.Http.Core.Response
*/
//Write the response data to the base stream
- return responseBlock.IsEmpty ? ValueTask.CompletedTask : transport.WriteAsync(responseBlock);
+ return responseBlock.IsEmpty ? ValueTask.CompletedTask : transport.Stream.WriteAsync(responseBlock);
}
/// <summary>
@@ -261,15 +258,14 @@ namespace VNLib.Net.Http.Core.Response
//reset after getting the written buffer
_headerWriterPosition = 0;
-
- //get base stream
- Stream bs = ContextInfo.GetTransport();
-
+
//Write the response data to the base stream
- await bs.WriteAsync(responseBlock);
+ await transport.Stream.WriteAsync(responseBlock);
- //Flush the base stream to send the data to the client
- await bs.FlushAsync();
+ /*
+ * Force flush should send data to client
+ */
+ await transport.FlushAsync();
}
/// <summary>
@@ -298,21 +294,11 @@ namespace VNLib.Net.Http.Core.Response
///<inheritdoc/>
public void OnRelease()
{
- ReusableChunkedStream.OnRelease();
- ReusableDirectStream.OnRelease();
Cookies.TrimExcess(DefaultCookieCapacity);
}
///<inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void OnNewConnection(Stream transport)
- {
- ReusableChunkedStream.OnNewConnection(transport);
- ReusableDirectStream.OnNewConnection(transport);
- }
-
- ///<inheritdoc/>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void OnNewRequest()
{
//Default to okay status code
@@ -340,16 +326,16 @@ namespace VNLib.Net.Http.Core.Response
ReusableChunkedStream.OnComplete();
}
- private sealed class DirectStream : ReusableResponseStream, IDirectResponsWriter
+ private sealed class DirectStream(TransportManager transport) : IDirectResponsWriter
{
///<inheritdoc/>
- public ValueTask WriteAsync(ReadOnlyMemory<byte> buffer) => transport!.WriteAsync(buffer);
+ public ValueTask WriteAsync(ReadOnlyMemory<byte> buffer) => transport!.Stream.WriteAsync(buffer);
}
/// <summary>
/// Writes chunked HTTP message bodies to an underlying streamwriter
/// </summary>
- private sealed class ChunkedStream(IChunkAccumulatorBuffer buffer, IHttpContextInformation context) : ReusableResponseStream, IResponseDataWriter
+ private sealed class ChunkedStream(IChunkAccumulatorBuffer buffer, TransportManager transport, IHttpContextInformation context) : IResponseDataWriter
{
private readonly ChunkDataAccumulator _chunkAccumulator = new(buffer, context);
@@ -389,7 +375,7 @@ namespace VNLib.Net.Http.Core.Response
_accumulatedBytes = 0;
//Write remaining data to stream
- return transport!.WriteAsync(chunkData, CancellationToken.None);
+ return transport.Stream.WriteAsync(chunkData, CancellationToken.None);
}
#endregion
diff --git a/lib/Net.Http/src/Core/Response/ReusableResponseStream.cs b/lib/Net.Http/src/Core/Response/ReusableResponseStream.cs
deleted file mode 100644
index 3070c82..0000000
--- a/lib/Net.Http/src/Core/Response/ReusableResponseStream.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Net.Http
-* File: ReusableResponseStream.cs
-*
-* ReusableResponseStream.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;
-
-namespace VNLib.Net.Http.Core.Response
-{
-
- internal abstract class ReusableResponseStream
- {
- protected Stream? transport;
-
- /// <summary>
- /// Called when a new connection is established
- /// </summary>
- /// <param name="transport"></param>
- public virtual void OnNewConnection(Stream transport) => this.transport = transport;
-
- /// <summary>
- /// Called when the connection is released
- /// </summary>
- public virtual void OnRelease() => transport = null;
-
- }
-} \ 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<byte> _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<byte> 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<byte> Writer;
+#endif
+
+#nullable restore
+
+ public Task FlushAsync() => Stream.FlushAsync();
+
+ /// <summary>
+ /// Assigns a new transport stream to the wrapper
+ /// as a new connection is assigned to write responses to
+ /// </summary>
+ /// <param name="transportStream">The transport stream to wrap</param>
+ public void OnNewConnection(Stream transportStream)
+ {
+ Stream = transportStream;
+
+ //Capture a buffer writer if the incoming stream supports direct writing
+ if (transportStream is IBufferWriter<byte> bw)
+ {
+ Writer = bw;
+ IsBufferWriter = true;
+ }
+ }
+
+ /// <summary>
+ /// Closes the current connection and resets the transport stream
+ /// </summary>
+ public void OnRelease()
+ {
+ Stream = null;
+ Writer = null;
+ IsBufferWriter = false;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/TransportReader.cs b/lib/Net.Http/src/Core/TransportReader.cs
index 0b38121..7cd8b8b 100644
--- a/lib/Net.Http/src/Core/TransportReader.cs
+++ b/lib/Net.Http/src/Core/TransportReader.cs
@@ -45,16 +45,14 @@ namespace VNLib.Net.Http.Core
/// <param name="buffer">The shared binary buffer</param>
/// <param name="encoding">The encoding to use when reading bianry</param>
/// <param name="lineTermination">The line delimiter to search for</param>
- internal struct TransportReader(Stream transport, IHttpHeaderParseBuffer buffer, Encoding encoding, ReadOnlyMemory<byte> lineTermination) : IVnTextReader
+ internal struct TransportReader(Stream transport, IHttpHeaderParseBuffer buffer, Encoding encoding, ReadOnlyMemory<byte> lineTermination)
+ : IVnTextReader
{
///<inheritdoc/>
public readonly Encoding Encoding => encoding;
///<inheritdoc/>
- public readonly ReadOnlyMemory<byte> LineTermination => lineTermination;
-
- ///<inheritdoc/>
- public readonly Stream BaseStream => transport;
+ public readonly ReadOnlyMemory<byte> LineTermination => lineTermination;
private readonly uint MaxBufferSize = (uint)buffer.BinSize;
@@ -79,15 +77,23 @@ namespace VNLib.Net.Http.Core
///<inheritdoc/>
public void Advance(int count)
{
- if (count < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(count), "Count must be positive");
- }
+ ArgumentOutOfRangeException.ThrowIfNegative(count);
//Advance the window start by the count and set the position
_position = _position.AdvanceStart(count);
}
+ /// <summary>
+ /// Sets the number of bytes to read from the transport stream
+ /// </summary>
+ /// <param name="start">The number of bytes to make available within the buffer window</param>
+ public void SetAvailableData(int start)
+ {
+ Debug.Assert(start <= MaxBufferSize, "Stream buffer would overflow");
+
+ _position = _position.AdvanceEnd(start);
+ }
+
///<inheritdoc/>
public void FillBuffer()
{
@@ -110,7 +116,7 @@ namespace VNLib.Net.Http.Core
{
//Get a ref to the entire buffer segment, then do an in-place move to shift the data to the start of the buffer
ref byte ptr = ref buffer.DangerousGetBinRef(0);
- MemoryUtil.Memmove(ref ptr, _position.WindowStart, ref ptr, 0, windowSize);
+ MemoryUtil.Memmove(ref ptr, _position.WindowStart, ref ptr, dstOffset: 0, windowSize);
/*
* Now that data has been shifted, update the position to
diff --git a/lib/Net.Http/src/HttpConfig.cs b/lib/Net.Http/src/HttpConfig.cs
index c74bdbb..40e9f88 100644
--- a/lib/Net.Http/src/HttpConfig.cs
+++ b/lib/Net.Http/src/HttpConfig.cs
@@ -25,6 +25,7 @@
using System;
using System.Text;
+using VNLib.Net.Http.Core;
using VNLib.Utils.Logging;
namespace VNLib.Net.Http
@@ -32,15 +33,51 @@ namespace VNLib.Net.Http
/// <summary>
/// Represents configration variables used to create the instance and manage http connections
/// </summary>
- /// <param name="ServerLog">
- /// A log provider that all server related log entiries will be written to
- /// </param>
- /// <param name="MemoryPool">
- /// Server memory pool to use for allocating buffers
- /// </param>
- public readonly record struct HttpConfig(ILogProvider ServerLog, IHttpMemoryPool MemoryPool)
+ public readonly record struct HttpConfig
{
-
+ /// <summary>
+ /// Pre-encoded CRLF bytes
+ /// </summary>
+ internal readonly HttpEncodedSegment CrlfBytes;
+
+ /// <summary>
+ /// Pre-encoded HTTP chunking final chunk segment
+ /// </summary>
+ internal readonly HttpEncodedSegment FinalChunkBytes;
+
+ /// <summary>
+ /// The cached header-line termination value
+ /// </summary>
+ internal readonly ReadOnlyMemory<byte> HeaderLineTermination;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HttpConfig"/> struct
+ /// </summary>
+ /// <param name="httpEncoding"></param>
+ public HttpConfig(Encoding httpEncoding)
+ {
+ ArgumentNullException.ThrowIfNull(httpEncoding);
+
+ HttpEncoding = httpEncoding;
+
+ //Init pre-encded segments
+ CrlfBytes = HttpEncodedSegment.FromString(HttpHelpers.CRLF, httpEncoding);
+ FinalChunkBytes = HttpEncodedSegment.FromString("0\r\n\r\n", httpEncoding);
+
+ //Store a ref to the crlf memory segment
+ HeaderLineTermination = CrlfBytes.Buffer.AsMemory();
+ }
+
+ /// <summary>
+ /// A log provider that all server related log entiries will be written to
+ /// </summary>
+ public required readonly ILogProvider ServerLog { get; init; }
+
+ /// <summary>
+ /// Server memory pool to use for allocating buffers
+ /// </summary>
+ public required readonly IHttpMemoryPool MemoryPool { get; init; }
+
/// <summary>
/// The absolute request entity body size limit in bytes
/// </summary>