aboutsummaryrefslogtreecommitdiff
path: root/lib/Net.Http
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-05-10 21:57:31 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-05-10 21:57:31 -0400
commit5e1f4cf3f8bcc64114478e1547822a2f5295f6ae (patch)
tree7fe77901db253cc7bc5e992a2ba400072ad66fe2 /lib/Net.Http
parent067c692800970e6fc41fbd0df669a6b1d6a07c55 (diff)
Yuge http buffering overhaul
Diffstat (limited to 'lib/Net.Http')
-rw-r--r--lib/Net.Http/src/Core/Buffering/ContextLockedBufferManager.cs247
-rw-r--r--lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs81
-rw-r--r--lib/Net.Http/src/Core/Buffering/IChunkAccumulatorBuffer.cs32
-rw-r--r--lib/Net.Http/src/Core/Buffering/IHttpBuffer.cs52
-rw-r--r--lib/Net.Http/src/Core/Buffering/IHttpBufferManager.cs97
-rw-r--r--lib/Net.Http/src/Core/Buffering/IHttpHeaderParseBuffer.cs35
-rw-r--r--lib/Net.Http/src/Core/Buffering/IResponseHeaderAccBuffer.cs33
-rw-r--r--lib/Net.Http/src/Core/Buffering/ISplitHttpBuffer.cs46
-rw-r--r--lib/Net.Http/src/Core/Buffering/SplitHttpBufferElement.cs70
-rw-r--r--lib/Net.Http/src/Core/HttpContext.cs84
-rw-r--r--lib/Net.Http/src/Core/HttpServerBase.cs13
-rw-r--r--lib/Net.Http/src/Core/HttpServerProcessing.cs64
-rw-r--r--lib/Net.Http/src/Core/IHttpContextInformation.cs59
-rw-r--r--lib/Net.Http/src/Core/Request/HttpInputStream.cs34
-rw-r--r--lib/Net.Http/src/Core/Request/HttpRequest.cs28
-rw-r--r--lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs416
-rw-r--r--lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs23
-rw-r--r--lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs134
-rw-r--r--lib/Net.Http/src/Core/Response/ChunkedStream.cs69
-rw-r--r--lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs155
-rw-r--r--lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs40
-rw-r--r--lib/Net.Http/src/Core/Response/HttpResponse.cs218
-rw-r--r--lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs86
-rw-r--r--lib/Net.Http/src/Core/TransportReader.cs (renamed from lib/Net.Http/src/Helpers/TransportReader.cs)28
-rw-r--r--lib/Net.Http/src/FileUpload.cs96
-rw-r--r--lib/Net.Http/src/Helpers/CoreBufferHelpers.cs77
-rw-r--r--lib/Net.Http/src/Helpers/HttpHelpers.cs17
-rw-r--r--lib/Net.Http/src/HttpBufferConfig.cs82
-rw-r--r--lib/Net.Http/src/HttpConfig.cs88
-rw-r--r--lib/Net.Http/src/IHttpEvent.cs (renamed from lib/Net.Http/src/Core/IHttpEvent.cs)5
-rw-r--r--lib/Net.Http/src/IHttpMemoryPool.cs52
31 files changed, 1748 insertions, 813 deletions
diff --git a/lib/Net.Http/src/Core/Buffering/ContextLockedBufferManager.cs b/lib/Net.Http/src/Core/Buffering/ContextLockedBufferManager.cs
new file mode 100644
index 0000000..25366ad
--- /dev/null
+++ b/lib/Net.Http/src/Core/Buffering/ContextLockedBufferManager.cs
@@ -0,0 +1,247 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ContextLockedBufferManager.cs
+*
+* ContextLockedBufferManager.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/.
+*/
+
+/*
+ * This file implements the IHttpBufferManager interface, which provides
+ * all the required buffers for http processing flow. The design was to allocate
+ * a single large buffer for the entire context and then slice it up into
+ * smaller segments for each buffer use case. Some buffers are shared between
+ * operations to reduce total memory usage when it is known that buffer usage
+ * will not conflict.
+ */
+
+using System;
+using System.Buffers;
+
+using VNLib.Utils.Memory;
+
+namespace VNLib.Net.Http.Core.Buffering
+{
+
+ internal class ContextLockedBufferManager : IHttpBufferManager
+ {
+ private readonly HttpBufferConfig Config;
+ private readonly int TotalBufferSize;
+
+ private readonly HeaderAccumulatorBuffer _requestHeaderBuffer;
+ private readonly HeaderAccumulatorBuffer _responseHeaderBuffer;
+ private readonly ChunkAccBuffer _chunkAccBuffer;
+
+ public ContextLockedBufferManager(in HttpBufferConfig config)
+ {
+ Config = config;
+
+ //Compute total buffer size from server config
+ TotalBufferSize = ComputeTotalBufferSize(in config);
+
+ /*
+ * 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 IMemoryOwner<byte>? _handle;
+ private HttpBufferSegments<byte> _segments;
+
+ #region LifeCycle
+
+ ///<inheritdoc/>
+ public void AllocateBuffer(IHttpMemoryPool allocator)
+ {
+ //Alloc a single buffer for the entire context
+ _handle = allocator.AllocateBufferForContext(TotalBufferSize);
+
+ try
+ {
+ 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);
+
+ //Discard/form data buffer
+ int discardAndFormDataSize = ComputeDiscardFormataBufferSize(in Config);
+
+ //Slice and store the buffer segments
+ _segments = new()
+ {
+ //Shared header buffer
+ HeaderAccumulator = GetNextSegment(ref full, headerParseBufferSize),
+
+ //Shared discard buffer and form data buffer
+ DiscardAndFormData = GetNextSegment(ref full, discardAndFormDataSize),
+
+ //Buffers cannot be shared
+ ChunkedResponseAccumulator = GetNextSegment(ref full, Config.ChunkedResponseAccumulatorSize),
+
+ ResponseBuffer = GetNextSegment(ref full, Config.ResponseBufferSize),
+ };
+
+ /*
+ * ************* WARNING ****************
+ *
+ * Request header and response header buffers are shared
+ * because they are assumed to be used in a single threaded context
+ * and control flow never allows them to be used at the same time.
+ *
+ * The bin buffer size is determined by the buffer config so the
+ * user may still configure the buffer size for restriction, so we
+ * just alloc the largerest of the two and use it for requests and
+ * responses.
+ *
+ * Control flow may change and become unsafe in the future!
+ */
+
+ _requestHeaderBuffer.SetBuffer(_segments.HeaderAccumulator);
+ _responseHeaderBuffer.SetBuffer(_segments.HeaderAccumulator);
+
+ //Chunk buffer will be used at the same time as the response buffer and discard buffers
+ _chunkAccBuffer.SetBuffer(_segments.ChunkedResponseAccumulator);
+ }
+ catch
+ {
+ //Free buffer on error
+ _handle.Dispose();
+ _handle = null;
+ throw;
+ }
+ }
+
+ ///<inheritdoc/>
+ public void ZeroAll()
+ {
+ //Zero the buffer completely
+ MemoryUtil.InitializeBlock(_handle.Memory);
+ }
+
+ ///<inheritdoc/>
+ public void FreeAll()
+ {
+ //Clear buffer memory structs to allow gc
+ _requestHeaderBuffer.FreeBuffer();
+ _responseHeaderBuffer.FreeBuffer();
+ _chunkAccBuffer.FreeBuffer();
+
+ //Clear segments
+ _segments = default;
+
+ //Free buffer
+ if (_handle != null)
+ {
+ _handle.Dispose();
+ _handle = null;
+ }
+ }
+
+ #endregion
+
+ ///<inheritdoc/>
+ public IHttpHeaderParseBuffer RequestHeaderParseBuffer => _requestHeaderBuffer;
+
+ ///<inheritdoc/>
+ public IResponseHeaderAccBuffer ResponseHeaderBuffer => _responseHeaderBuffer;
+
+ ///<inheritdoc/>
+ public IChunkAccumulatorBuffer ChunkAccumulatorBuffer => _chunkAccBuffer;
+
+
+ /*
+ * Discard buffer may be used for form-data parsing as they will never be used at
+ * the same time during normal operation
+ */
+
+ ///<inheritdoc/>
+ public Memory<byte> GetFormDataBuffer() => _segments.DiscardAndFormData;
+
+ ///<inheritdoc/>
+ public Memory<byte> GetDiscardBuffer() => _segments.DiscardAndFormData;
+
+ ///<inheritdoc/>
+ public Memory<byte> GetResponseDataBuffer() => _segments.ResponseBuffer;
+
+
+
+ static Memory<byte> GetNextSegment(ref Memory<byte> buffer, int size)
+ {
+ //get segment from current slice
+ Memory<byte> segment = buffer[..size];
+
+ //Upshift buffer
+ buffer = buffer[size..];
+
+ return segment;
+ }
+
+ /*
+ * Computes the correct size of the request header buffer from the config
+ * so it is large enough to hold the binary buffer but also the split char
+ * buffer
+ */
+
+ static int GetMaxHeaderBufferSize(in HttpBufferConfig config)
+ {
+ int max = Math.Max(config.RequestHeaderBufferSize, config.ResponseHeaderBufferSize);
+
+ //Compute the max size including the char buffer
+ return SplitHttpBufferElement.GetfullSize(max);
+ }
+
+ static int ComputeTotalBufferSize(in HttpBufferConfig config)
+ {
+ return config.ResponseBufferSize
+ + config.ChunkedResponseAccumulatorSize
+ + ComputeDiscardFormataBufferSize(in config)
+ + GetMaxHeaderBufferSize(in config); //Header buffers are shared
+ }
+
+ static int ComputeDiscardFormataBufferSize(in HttpBufferConfig config)
+ {
+ //Get the larger of the two buffers, so it can be shared between the two
+ return Math.Max(config.DiscardBufferSize, config.FormDataBufferSize);
+ }
+
+
+ readonly record struct HttpBufferSegments<T>
+ {
+ public readonly Memory<T> HeaderAccumulator { get; init; }
+ public readonly Memory<T> ChunkedResponseAccumulator { get; init; }
+ public readonly Memory<T> DiscardAndFormData { get; init; }
+ public readonly Memory<T> ResponseBuffer { get; init; }
+ }
+
+
+ private sealed class HeaderAccumulatorBuffer: SplitHttpBufferElement, IResponseHeaderAccBuffer, IHttpHeaderParseBuffer
+ {
+ public HeaderAccumulatorBuffer(int binSize):base(binSize)
+ { }
+ }
+
+ 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
new file mode 100644
index 0000000..0e60cae
--- /dev/null
+++ b/lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs
@@ -0,0 +1,81 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpBufferElement.cs
+*
+* HttpBufferElement.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;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.Memory;
+
+namespace VNLib.Net.Http.Core.Buffering
+{
+ /*
+ * Abstract class for controlled access to the raw buffer block
+ * as we are pinning the block. The block is pinned once for the lifetime
+ * of the connection, so we have access to the raw memory for faster
+ * span access.
+ */
+ internal abstract class HttpBufferElement : IHttpBuffer
+ {
+
+ public virtual void FreeBuffer()
+ {
+ //Unpin and set defaults
+ Pinned.Dispose();
+ Pinned = default;
+ Buffer = default;
+ Size = 0;
+ }
+
+ public virtual void SetBuffer(Memory<byte> buffer)
+ {
+ //Set mem buffer
+ Buffer = buffer;
+ //Pin buffer and hold handle
+ Pinned = buffer.Pin();
+ //Set size to length of buffer
+ Size = buffer.Length;
+ }
+
+ ///<inheritdoc/>
+ public int Size { get; private set; }
+
+ private MemoryHandle Pinned;
+ protected Memory<byte> Buffer;
+
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public virtual Span<byte> GetBinSpan() => MemoryUtil.GetSpan<byte>(Pinned, Size);
+
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public virtual Memory<byte> GetMemory() => Buffer;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected virtual Span<byte> GetBinSpan(int maxSize)
+ {
+ return maxSize > Size ? throw new ArgumentOutOfRangeException(nameof(maxSize)) : MemoryUtil.GetSpan<byte>(Pinned, maxSize);
+ }
+ }
+}
diff --git a/lib/Net.Http/src/Core/Buffering/IChunkAccumulatorBuffer.cs b/lib/Net.Http/src/Core/Buffering/IChunkAccumulatorBuffer.cs
new file mode 100644
index 0000000..12ce5f9
--- /dev/null
+++ b/lib/Net.Http/src/Core/Buffering/IChunkAccumulatorBuffer.cs
@@ -0,0 +1,32 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IChunkAccumulatorBuffer.cs
+*
+* IChunkAccumulatorBuffer.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/.
+*/
+
+namespace VNLib.Net.Http.Core.Buffering
+{
+ /// <summary>
+ /// Represents a binary only chunk accumulator buffer
+ /// </summary>
+ internal interface IChunkAccumulatorBuffer : IHttpBuffer
+ { }
+}
diff --git a/lib/Net.Http/src/Core/Buffering/IHttpBuffer.cs b/lib/Net.Http/src/Core/Buffering/IHttpBuffer.cs
new file mode 100644
index 0000000..07c7618
--- /dev/null
+++ b/lib/Net.Http/src/Core/Buffering/IHttpBuffer.cs
@@ -0,0 +1,52 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHttpBuffer.cs
+*
+* IHttpBuffer.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;
+
+namespace VNLib.Net.Http.Core.Buffering
+{
+ /// <summary>
+ /// Represents a buffer segment use for an http operation, and defines a shared set of
+ /// methods used for capturing safe buffer segments.
+ /// </summary>
+ internal interface IHttpBuffer
+ {
+ /// <summary>
+ /// Gets the internal buffer as a span of bytes as fast as possible
+ /// </summary>
+ /// <returns>The memory block as a span</returns>
+ Span<byte> GetBinSpan();
+
+ /// <summary>
+ /// Gets the internal buffer as a memory block as fast as possible
+ /// </summary>
+ /// <returns>The memory block</returns>
+ Memory<byte> GetMemory();
+
+ /// <summary>
+ /// The size of the internal buffer
+ /// </summary>
+ int Size { get; }
+ }
+}
diff --git a/lib/Net.Http/src/Core/Buffering/IHttpBufferManager.cs b/lib/Net.Http/src/Core/Buffering/IHttpBufferManager.cs
new file mode 100644
index 0000000..445aa6f
--- /dev/null
+++ b/lib/Net.Http/src/Core/Buffering/IHttpBufferManager.cs
@@ -0,0 +1,97 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHttpBufferManager.cs
+*
+* IHttpBufferManager.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;
+
+namespace VNLib.Net.Http.Core.Buffering
+{
+
+ /// <summary>
+ /// <para>
+ /// Represents an internal http buffer manager which manages the allocation and deallocation of internal buffers
+ /// for specific http operations.
+ /// </para>
+ /// <para>
+ /// Methods are considered on-demand and should be called only when the buffer is needed.
+ /// </para>
+ /// <para>
+ /// Properties are considered persistent, however properties and method return values
+ /// are considered on-demand.
+ /// </para>
+ /// </summary>
+ /// <remarks>
+ /// This abstraction assumes that the buffer manager is used in a single-threaded context.
+ /// </remarks>
+ internal interface IHttpBufferManager
+ {
+ /// <summary>
+ /// Gets the independent buffer block used to buffer response data
+ /// </summary>
+ /// <returns>The memory block used for buffering application response data</returns>
+ Memory<byte> GetResponseDataBuffer();
+
+ /// <summary>
+ /// Gets the independent buffer used to discard data request data
+ /// </summary>
+ /// <returns>The memory block used for discarding request data</returns>
+ Memory<byte> GetDiscardBuffer();
+
+ /// <summary>
+ /// Gets a buffer used for buffering form-data
+ /// </summary>
+ /// <returns>The memory block</returns>
+ Memory<byte> GetFormDataBuffer();
+
+ /// <summary>
+ /// Gets the request header parsing buffer element
+ /// </summary>
+ IHttpHeaderParseBuffer RequestHeaderParseBuffer { get; }
+
+ /// <summary>
+ /// Gets the response header accumulator buffer element
+ /// </summary>
+ IResponseHeaderAccBuffer ResponseHeaderBuffer { get; }
+
+ /// <summary>
+ /// Gets the chunk accumulator buffer element
+ /// </summary>
+ IChunkAccumulatorBuffer ChunkAccumulatorBuffer { get; }
+
+ /// <summary>
+ /// Alloctes internal buffers from the given <see cref="IHttpMemoryPool"/>
+ /// </summary>
+ /// <param name="allocator">The pool to allocate memory from</param>
+ void AllocateBuffer(IHttpMemoryPool allocator);
+
+ /// <summary>
+ /// Zeros all internal buffers
+ /// </summary>
+ void ZeroAll();
+
+ /// <summary>
+ /// Frees all internal buffers
+ /// </summary>
+ void FreeAll();
+ }
+}
diff --git a/lib/Net.Http/src/Core/Buffering/IHttpHeaderParseBuffer.cs b/lib/Net.Http/src/Core/Buffering/IHttpHeaderParseBuffer.cs
new file mode 100644
index 0000000..521fcfa
--- /dev/null
+++ b/lib/Net.Http/src/Core/Buffering/IHttpHeaderParseBuffer.cs
@@ -0,0 +1,35 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHttpHeaderParseBuffer.cs
+*
+* IHttpHeaderParseBuffer.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/.
+*/
+
+
+namespace VNLib.Net.Http.Core.Buffering
+{
+ /// <summary>
+ /// A split buffer element that is used to parse http request headers
+ /// </summary>
+ internal interface IHttpHeaderParseBuffer : ISplitHttpBuffer
+ {
+
+ }
+}
diff --git a/lib/Net.Http/src/Core/Buffering/IResponseHeaderAccBuffer.cs b/lib/Net.Http/src/Core/Buffering/IResponseHeaderAccBuffer.cs
new file mode 100644
index 0000000..71b1042
--- /dev/null
+++ b/lib/Net.Http/src/Core/Buffering/IResponseHeaderAccBuffer.cs
@@ -0,0 +1,33 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IResponseHeaderAccBuffer.cs
+*
+* IResponseHeaderAccBuffer.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/.
+*/
+
+
+namespace VNLib.Net.Http.Core.Buffering
+{
+ /// <summary>
+ /// Represents a split response header accumulator buffer
+ /// </summary>
+ internal interface IResponseHeaderAccBuffer : ISplitHttpBuffer
+ { }
+}
diff --git a/lib/Net.Http/src/Core/Buffering/ISplitHttpBuffer.cs b/lib/Net.Http/src/Core/Buffering/ISplitHttpBuffer.cs
new file mode 100644
index 0000000..2e0963d
--- /dev/null
+++ b/lib/Net.Http/src/Core/Buffering/ISplitHttpBuffer.cs
@@ -0,0 +1,46 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ISplitHttpBuffer.cs
+*
+* ISplitHttpBuffer.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;
+
+namespace VNLib.Net.Http.Core.Buffering
+{
+ /// <summary>
+ /// Represents a buffer manager that contains segments for binary and character buffers
+ /// </summary>
+ internal interface ISplitHttpBuffer : IHttpBuffer
+ {
+ /// <summary>
+ /// Gets the character segment of the internal buffer as a span of chars, which may be slower than <see cref="IHttpBuffer.GetBinSpan"/>
+ /// but still considered a hot-path
+ /// </summary>
+ /// <returns>The character segment of the internal buffer</returns>
+ Span<char> GetCharSpan();
+
+ /// <summary>
+ /// The size of the internal binary buffer segment
+ /// </summary>
+ int BinSize { get; }
+ }
+}
diff --git a/lib/Net.Http/src/Core/Buffering/SplitHttpBufferElement.cs b/lib/Net.Http/src/Core/Buffering/SplitHttpBufferElement.cs
new file mode 100644
index 0000000..e65ffd8
--- /dev/null
+++ b/lib/Net.Http/src/Core/Buffering/SplitHttpBufferElement.cs
@@ -0,0 +1,70 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: SplitHttpBufferElement.cs
+*
+* SplitHttpBufferElement.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;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace VNLib.Net.Http.Core.Buffering
+{
+ internal abstract class SplitHttpBufferElement : HttpBufferElement, ISplitHttpBuffer
+ {
+ ///<inheritdoc/>
+ public int BinSize { get; }
+
+ internal SplitHttpBufferElement(int binSize)
+ {
+ BinSize = binSize;
+ }
+
+ ///<inheritdoc/>
+ public Span<char> GetCharSpan()
+ {
+ //Get full buffer span
+ Span<byte> _base = base.GetBinSpan();
+
+ //Upshift to end of bin buffer
+ _base = _base[BinSize..];
+
+ //Return char span
+ return MemoryMarshal.Cast<byte, char>(_base);
+ }
+
+ /*
+ * Override to trim the bin buffer to the actual size of the
+ * binary segment of the buffer
+ */
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override Span<byte> GetBinSpan() => base.GetBinSpan(BinSize);
+
+
+ /// <summary>
+ /// Gets the size total of the buffer required for binary data and char data
+ /// </summary>
+ /// <param name="binSize">The desired size of the binary buffer</param>
+ /// <returns>The total size of the binary buffer required to store the binary and character buffer</returns>
+ public static int GetfullSize(int binSize) => binSize + (binSize * sizeof(char));
+ }
+}
diff --git a/lib/Net.Http/src/Core/HttpContext.cs b/lib/Net.Http/src/Core/HttpContext.cs
index 7e34dff..cdc3554 100644
--- a/lib/Net.Http/src/Core/HttpContext.cs
+++ b/lib/Net.Http/src/Core/HttpContext.cs
@@ -24,15 +24,15 @@
using System;
using System.IO;
-using System.Runtime.CompilerServices;
+using System.Text;
using VNLib.Utils;
using VNLib.Utils.Memory.Caching;
-
+using VNLib.Net.Http.Core.Buffering;
namespace VNLib.Net.Http.Core
{
- internal sealed partial class HttpContext : IConnectionContext, IReusable
+ internal sealed partial class HttpContext : IConnectionContext, IReusable, IHttpContextInformation
{
/// <summary>
/// When set as a response flag, disables response compression for
@@ -52,10 +52,6 @@ namespace VNLib.Net.Http.Core
/// The http server that this context is bound to
/// </summary>
public readonly HttpServer ParentServer;
- /// <summary>
- /// The shared transport header reader buffer
- /// </summary>
- public readonly SharedHeaderReaderBuffer RequestBuffer;
/// <summary>
/// The response entity body container
@@ -69,6 +65,11 @@ namespace VNLib.Net.Http.Core
public readonly BitField ContextFlags;
/// <summary>
+ /// The internal buffer manager for the context
+ /// </summary>
+ public readonly ContextLockedBufferManager Buffers;
+
+ /// <summary>
/// Gets or sets the alternate application protocol to swtich to
/// </summary>
/// <remarks>
@@ -77,39 +78,26 @@ namespace VNLib.Net.Http.Core
/// or this property must be exlicitly cleared
/// </remarks>
public IAlternateProtocol? AlternateProtocol { get; set; }
+
private readonly ResponseWriter responseWriter;
private ITransportContext? _ctx;
public HttpContext(HttpServer server)
{
- /*
- * Local method for retreiving the transport stream,
- * this adds protection/debug from response/request
- * containers not allowed to maintain referrences
- * to a transport stream after it has been released
- */
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- Stream GetStream() => _ctx!.ConnectionStream;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- HttpVersion GetVersion() => Request.HttpVersion;
-
ParentServer = server;
+ //Store CRLF bytes
+ CrlfBytes = server.Config.HttpEncoding.GetBytes(HttpHelpers.CRLF);
+
+ //Init buffer manager
+ Buffers = new(server.Config.BufferConfig);
+
//Create new request
- Request = new HttpRequest(GetStream);
+ Request = new HttpRequest(this);
//create a new response object
- Response = new HttpResponse(
- server.Config.HttpEncoding,
- ParentServer.Config.ResponseHeaderBufferSize,
- ParentServer.Config.ChunkedResponseAccumulatorSize,
- GetStream,
- GetVersion);
-
- //The shared request parsing buffer
- RequestBuffer = new(server.Config.HeaderBufferSize);
+ Response = new HttpResponse(Buffers, this);
//Init response writer
ResponseBody = responseWriter = new ResponseWriter();
@@ -118,12 +106,31 @@ namespace VNLib.Net.Http.Core
}
public TransportSecurityInfo? GetSecurityInfo() => _ctx?.GetSecurityInfo();
-
+
+ #region Context information
+
+ ///<inheritdoc/>
+ public ReadOnlyMemory<byte> CrlfBytes { get; }
+
+ ///<inheritdoc/>
+ Encoding IHttpContextInformation.Encoding => ParentServer.Config.HttpEncoding;
+
+ ///<inheritdoc/>
+ HttpVersion IHttpContextInformation.CurrentVersion => Request.HttpVersion;
+
+ ///<inheritdoc/>
+ HttpConfig IHttpContextInformation.Config => ParentServer.Config;
+
+ ///<inheritdoc/>
+ Stream IHttpContextInformation.GetTransport() => _ctx!.ConnectionStream;
+
+ #endregion
#region LifeCycle Hooks
///<inheritdoc/>
public void InitializeContext(ITransportContext ctx) => _ctx = ctx;
+
///<inheritdoc/>
public void BeginRequest()
@@ -134,7 +141,6 @@ namespace VNLib.Net.Http.Core
//Lifecycle on new request
Request.OnNewRequest();
Response.OnNewRequest();
- RequestBuffer.OnNewRequest();
//Initialize the request
Request.Initialize(_ctx!, ParentServer.Config.DefaultHttpVersion);
@@ -145,15 +151,16 @@ namespace VNLib.Net.Http.Core
{
Request.OnComplete();
Response.OnComplete();
- RequestBuffer.OnComplete();
responseWriter.OnComplete();
}
-
+
void IReusable.Prepare()
{
Request.OnPrepare();
Response.OnPrepare();
- RequestBuffer.OnPrepare();
+
+ //Alloc buffers
+ Buffers.AllocateBuffer(ParentServer.Config.MemoryPool);
}
bool IReusable.Release()
@@ -165,11 +172,16 @@ namespace VNLib.Net.Http.Core
//Release response/requqests
Request.OnRelease();
Response.OnRelease();
- RequestBuffer.OnRelease();
+
+ //Zero before returning to pool
+ Buffers.ZeroAll();
+
+ //Free buffers
+ Buffers.FreeAll();
return true;
}
-
+
#endregion
}
} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/HttpServerBase.cs b/lib/Net.Http/src/Core/HttpServerBase.cs
index 3dac217..9283998 100644
--- a/lib/Net.Http/src/Core/HttpServerBase.cs
+++ b/lib/Net.Http/src/Core/HttpServerBase.cs
@@ -125,6 +125,7 @@ namespace VNLib.Net.Http
{
_ = conf.HttpEncoding ?? throw new ArgumentException("HttpEncoding cannot be null", nameof(conf));
_ = conf.ServerLog ?? throw new ArgumentException("ServerLog cannot be null", nameof(conf));
+ _ = conf.MemoryPool ?? throw new ArgumentNullException(nameof(conf));
if (conf.ActiveConnectionRecvTimeout < -1)
{
@@ -132,7 +133,7 @@ namespace VNLib.Net.Http
}
//Chunked data accumulator must be at least 64 bytes (arbinrary value)
- if (conf.ChunkedResponseAccumulatorSize < 64 || conf.ChunkedResponseAccumulatorSize == int.MaxValue)
+ if (conf.BufferConfig.ChunkedResponseAccumulatorSize < 64 || conf.BufferConfig.ChunkedResponseAccumulatorSize == int.MaxValue)
{
throw new ArgumentException("ChunkedResponseAccumulatorSize cannot be less than 64 bytes", nameof(conf));
}
@@ -152,17 +153,17 @@ namespace VNLib.Net.Http
throw new ArgumentException("DefaultHttpVersion cannot be NotSupported", nameof(conf));
}
- if (conf.DiscardBufferSize < 64)
+ if (conf.BufferConfig.DiscardBufferSize < 64)
{
throw new ArgumentException("DiscardBufferSize cannot be less than 64 bytes", nameof(conf));
}
- if (conf.FormDataBufferSize < 64)
+ if (conf.BufferConfig.FormDataBufferSize < 64)
{
throw new ArgumentException("FormDataBufferSize cannot be less than 64 bytes", nameof(conf));
}
- if (conf.HeaderBufferSize < 128)
+ if (conf.BufferConfig.RequestHeaderBufferSize < 128)
{
throw new ArgumentException("HeaderBufferSize cannot be less than 128 bytes", nameof(conf));
}
@@ -187,12 +188,12 @@ namespace VNLib.Net.Http
throw new ArgumentException("MaxUploadSize cannot be less than 0", nameof(conf));
}
- if (conf.ResponseBufferSize < 64)
+ if (conf.BufferConfig.ResponseBufferSize < 64)
{
throw new ArgumentException("ResponseBufferSize cannot be less than 64 bytes", nameof(conf));
}
- if (conf.ResponseHeaderBufferSize < 128)
+ if (conf.BufferConfig.ResponseHeaderBufferSize < 128)
{
throw new ArgumentException("ResponseHeaderBufferSize cannot be less than 128 bytes", nameof(conf));
}
diff --git a/lib/Net.Http/src/Core/HttpServerProcessing.cs b/lib/Net.Http/src/Core/HttpServerProcessing.cs
index 886f735..d054755 100644
--- a/lib/Net.Http/src/Core/HttpServerProcessing.cs
+++ b/lib/Net.Http/src/Core/HttpServerProcessing.cs
@@ -5,8 +5,8 @@
* Package: VNLib.Net.Http
* File: HttpServerProcessing.cs
*
-* HttpServerProcessing.cs is part of VNLib.Net.Http which is part of the larger
-* VNLib collection of libraries and utilities.
+* HttpServerProcessing.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
@@ -30,8 +30,10 @@ using System.Net.Sockets;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
+using VNLib.Utils.Memory;
using VNLib.Utils.Logging;
using VNLib.Net.Http.Core;
+using VNLib.Net.Http.Core.Buffering;
namespace VNLib.Net.Http
{
@@ -159,16 +161,18 @@ namespace VNLib.Net.Http
{
return false;
}
+
+ //process the request
+ bool keepalive = await ProcessRequestAsync(context, (HttpStatusCode)status);
+
#if DEBUG
- //Write debug request log
- if (Config.RequestDebugLog != null)
+ //Write debug response log
+ if(Config.RequestDebugLog != null)
{
- Config.RequestDebugLog.Verbose(context.Request.ToString());
+ WriteConnectionDebugLog(context);
}
#endif
- //process the request
- bool keepalive = await ProcessRequestAsync(context, (HttpStatusCode)status);
-
+
//Close the response
await context.WriteResponseAsync(StopToken!.Token);
@@ -185,6 +189,25 @@ namespace VNLib.Net.Http
}
}
+ private void WriteConnectionDebugLog(HttpContext context)
+ {
+ //Alloc debug buffer
+ using IMemoryHandle<char> debugBuffer = MemoryUtil.SafeAlloc<char>(16 * 1024);
+
+ ForwardOnlyWriter<char> writer = new (debugBuffer.Span);
+
+ //Request
+ context.Request.Compile(ref writer);
+
+ //newline
+ writer.Append("\r\n");
+
+ //Response
+ context.Response.Compile(ref writer);
+
+ Config.RequestDebugLog!.Verbose("\r\n{dbg}", writer.ToString());
+ }
+
/// <summary>
/// Reads data synchronously from the transport and attempts to parse an HTTP message and
/// built a request.
@@ -205,34 +228,41 @@ namespace VNLib.Net.Http
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private HttpStatusCode ParseRequest(ITransportContext transport, HttpContext ctx)
{
+ //Get the parse buffer
+ IHttpHeaderParseBuffer parseBuffer = ctx.Buffers.RequestHeaderParseBuffer;
+
//Init parser
- TransportReader reader = new (transport.ConnectionStream, ctx.RequestBuffer, Config.HttpEncoding, HeaderLineTermination);
+ TransportReader reader = new (transport.ConnectionStream, parseBuffer, Config.HttpEncoding, HeaderLineTermination);
try
{
- Span<char> lineBuf = ctx.RequestBuffer.CharBuffer;
+ //Get the char span
+ Span<char> lineBuf = parseBuffer.GetCharSpan();
Http11ParseExtensions.Http1ParseState parseState = new();
//Parse the request line
- HttpStatusCode code = ctx.Request.Http1ParseRequestLine(ref parseState, ref reader, in lineBuf);
+ HttpStatusCode code = ctx.Request.Http1ParseRequestLine(ref parseState, ref reader, lineBuf);
if (code > 0)
{
return code;
}
+
//Parse the headers
- code = ctx.Request.Http1ParseHeaders(ref parseState, ref reader, Config, in lineBuf);
+ code = ctx.Request.Http1ParseHeaders(ref parseState, ref reader, Config, lineBuf);
if (code > 0)
{
return code;
}
+
//Prepare entity body for request
code = ctx.Request.Http1PrepareEntityBody(ref parseState, ref reader, Config);
if (code > 0)
{
return code;
}
+
//Success!
return 0;
}
@@ -266,6 +296,7 @@ namespace VNLib.Net.Http
//exit and close connection (default result will close the context)
return false;
}
+
//We only support version 1 and 1/1
if ((context.Request.HttpVersion & (HttpVersion.Http11 | HttpVersion.Http1)) == 0)
{
@@ -274,6 +305,7 @@ namespace VNLib.Net.Http
context.Respond(HttpStatusCode.HttpVersionNotSupported);
return false;
}
+
//Check open connection count (not super accurate, or might not be atomic)
if (OpenConnectionCount > Config.MaxOpenConnections)
{
@@ -297,6 +329,7 @@ namespace VNLib.Net.Http
//Set connection closed
context.Response.Headers[HttpResponseHeader.Connection] = "closed";
}
+
//Get the server root for the specified location
if (!ServerRoots.TryGetValue(context.Request.Location.DnsSafeHost, out IWebRoot? root) && !ServerRoots.TryGetValue(WILDCARD_KEY, out root))
{
@@ -304,6 +337,7 @@ namespace VNLib.Net.Http
//make sure control leaves
return keepalive;
}
+
//check for redirects
if (root.Redirects.TryGetValue(context.Request.Location.LocalPath, out Redirect? r))
{
@@ -312,12 +346,14 @@ namespace VNLib.Net.Http
//Return keepalive
return keepalive;
}
+
//Check the expect header and return an early status code
if (context.Request.Expect)
{
//send a 100 status code
await context.Response.SendEarly100ContinueAsync();
}
+
/*
* Initialze the request body state, which may read/buffer the request
* entity body. When doing so, the only exceptions that should be
@@ -330,7 +366,9 @@ namespace VNLib.Net.Http
* form data size so oom or overflow would be bugs, and we can let
* them get thrown
*/
- await context.Request.InitRequestBodyAsync(Config.FormDataBufferSize, Config.HttpEncoding);
+
+ await context.InitRequestBodyAsync();
+
try
{
await ProcessAsync(root, context);
diff --git a/lib/Net.Http/src/Core/IHttpContextInformation.cs b/lib/Net.Http/src/Core/IHttpContextInformation.cs
new file mode 100644
index 0000000..2a6e10c
--- /dev/null
+++ b/lib/Net.Http/src/Core/IHttpContextInformation.cs
@@ -0,0 +1,59 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHttpContextInformation.cs
+*
+* IHttpContextInformation.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;
+using System.IO;
+using System.Text;
+
+namespace VNLib.Net.Http.Core
+{
+ internal interface IHttpContextInformation
+ {
+ /// <summary>
+ /// Local crlf characters
+ /// </summary>
+ ReadOnlyMemory<byte> CrlfBytes { get; }
+
+ /// <summary>
+ /// The current connection's encoding
+ /// </summary>
+ Encoding Encoding { get; }
+
+ /// <summary>
+ /// The current connection's http version
+ /// </summary>
+ HttpVersion CurrentVersion { get; }
+
+ /// <summary>
+ /// The current server configuration
+ /// </summary>
+ HttpConfig Config { 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/Request/HttpInputStream.cs b/lib/Net.Http/src/Core/Request/HttpInputStream.cs
index 3b929af..c591c6d 100644
--- a/lib/Net.Http/src/Core/Request/HttpInputStream.cs
+++ b/lib/Net.Http/src/Core/Request/HttpInputStream.cs
@@ -24,14 +24,13 @@
using System;
using System.IO;
-using System.Buffers;
using System.Threading;
using System.Threading.Tasks;
using VNLib.Utils;
-using VNLib.Utils.IO;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
+using VNLib.Net.Http.Core.Buffering;
namespace VNLib.Net.Http.Core
{
@@ -40,7 +39,7 @@ namespace VNLib.Net.Http.Core
/// </summary>
internal sealed class HttpInputStream : Stream
{
- private readonly Func<Stream> GetTransport;
+ private readonly IHttpContextInformation ContextInfo;
private long ContentLength;
private Stream? InputStream;
@@ -48,7 +47,7 @@ namespace VNLib.Net.Http.Core
private InitDataBuffer? _initalData;
- public HttpInputStream(Func<Stream> getTransport) => GetTransport = getTransport;
+ public HttpInputStream(IHttpContextInformation contextInfo) => ContextInfo = contextInfo;
internal void OnComplete()
{
@@ -79,7 +78,7 @@ namespace VNLib.Net.Http.Core
_initalData = initial;
//Cache transport
- InputStream = GetTransport();
+ InputStream = ContextInfo.GetTransport();
}
public override void Close() => throw new NotSupportedException("The HTTP input stream should never be closed!");
@@ -90,9 +89,10 @@ namespace VNLib.Net.Http.Core
public override long Length => ContentLength;
public override long Position { get => _position; set { } }
- public override void Flush(){}
+ public override void Flush() { }
public override int Read(byte[] buffer, int offset, int count) => Read(buffer.AsSpan(offset, count));
+
public override int Read(Span<byte> buffer)
{
//Calculate the amount of data that can be read into the buffer
@@ -143,6 +143,7 @@ namespace VNLib.Net.Http.Core
{
//Calculate the amount of data that can be read into the buffer
int bytesToRead = (int)Math.Min(buffer.Length, Remaining);
+
if (bytesToRead == 0)
{
return 0;
@@ -168,7 +169,7 @@ namespace VNLib.Net.Http.Core
if (writer.RemainingSize > 0)
{
//Read from transport
- ERRNO read = await InputStream!.ReadAsync(writer.Remaining, cancellationToken).ConfigureAwait(false);
+ ERRNO read = await InputStream!.ReadAsync(writer.Remaining, cancellationToken).ConfigureAwait(true);
//Update writer position
writer.Advance(read);
@@ -183,9 +184,9 @@ namespace VNLib.Net.Http.Core
/// <summary>
/// Asynchronously discards all remaining data in the stream
/// </summary>
- /// <param name="maxBufferSize">The maxium size of the buffer to allocate</param>
+ /// <param name="bufMan">The buffer manager to request the discard buffer from</param>
/// <returns>A task that represents the discard operations</returns>
- public async ValueTask DiscardRemainingAsync(int maxBufferSize)
+ public async ValueTask DiscardRemainingAsync(IHttpBufferManager bufMan)
{
long remaining = Remaining;
@@ -203,15 +204,14 @@ namespace VNLib.Net.Http.Core
//We must actaully disacrd data from the stream
else
{
- //Calcuate a buffer size to allocate (will never be larger than an int)
- int bufferSize = (int)Math.Min(remaining, maxBufferSize);
- //Alloc a discard buffer to reset the transport
- using IMemoryOwner<byte> discardBuffer = CoreBufferHelpers.GetMemory(bufferSize, false);
- int read = 0;
+ //Reqest discard buffer
+ Memory<byte> discardBuffer = bufMan.GetDiscardBuffer();
+
+ int read;
do
{
- //Read data to the discard buffer until reading is completed
- read = await ReadAsync(discardBuffer.Memory, CancellationToken.None).ConfigureAwait(true);
+ //Read data to the discard buffer until reading is completed (read == 0)
+ read = await ReadAsync(discardBuffer, CancellationToken.None).ConfigureAwait(true);
} while (read != 0);
}
@@ -222,7 +222,9 @@ namespace VNLib.Net.Http.Core
//Ignore seek control
return _position;
}
+
public override void SetLength(long value) => throw new NotSupportedException();
+
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
}
} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Request/HttpRequest.cs b/lib/Net.Http/src/Core/Request/HttpRequest.cs
index 356c3f6..f638ac9 100644
--- a/lib/Net.Http/src/Core/Request/HttpRequest.cs
+++ b/lib/Net.Http/src/Core/Request/HttpRequest.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -23,7 +23,6 @@
*/
using System;
-using System.IO;
using System.Net;
using System.Collections.Generic;
using System.Security.Authentication;
@@ -35,7 +34,7 @@ using VNLib.Utils.Extensions;
namespace VNLib.Net.Http.Core
{
- internal class HttpRequest : IHttpLifeCycle
+ internal sealed class HttpRequest : IHttpLifeCycle
#if DEBUG
,IStringSerializeable
#endif
@@ -75,7 +74,7 @@ namespace VNLib.Net.Http.Core
public bool Expect { get; set; }
#nullable disable
- public HttpRequest(Func<Stream> getTransport)
+ public HttpRequest(IHttpContextInformation contextInfo)
{
//Create new collection for headers
Headers = new();
@@ -85,7 +84,7 @@ namespace VNLib.Net.Http.Core
Accept = new();
AcceptLanguage = new();
//New reusable input stream
- InputStream = new(getTransport);
+ InputStream = new(contextInfo);
RequestBody = new();
}
@@ -134,10 +133,11 @@ namespace VNLib.Net.Http.Core
#if DEBUG
+
public string Compile()
{
//Alloc char buffer for compilation
- using UnsafeMemoryHandle<char> buffer = MemoryUtil.UnsafeAlloc<char>(16 * 1024, true);
+ using IMemoryHandle<char> buffer = MemoryUtil.SafeAlloc<char>(16 * 1024);
ForwardOnlyWriter<char> writer = new(buffer.Span);
@@ -171,7 +171,9 @@ namespace VNLib.Net.Http.Core
writer.Append("0.9");
break;
}
+
writer.Append("\r\n");
+
//write host
writer.Append("Host: ");
writer.Append(Location?.Authority);
@@ -185,6 +187,7 @@ namespace VNLib.Net.Http.Core
writer.Append(Headers[header]);
writer.Append("\r\n");
}
+
//Write cookies
foreach (string cookie in Cookies.Keys)
{
@@ -273,15 +276,12 @@ namespace VNLib.Net.Http.Core
Compile(ref writer);
return writer.Written;
}
- public override string ToString()
- {
- return Compile();
- }
+
+ public override string ToString() => Compile();
#else
- public override string ToString()
- {
- return "Request debug output only available when compiled in DEBUG mode";
- }
+
+ public override string ToString() => "HTTP Library was compiled without a DEBUG directive, request logging is not available";
+
#endif
}
} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs b/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
index 3841034..1f52d17 100644
--- a/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
+++ b/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
@@ -29,11 +29,10 @@ using System.Text;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
+using VNLib.Utils.IO;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
-using static VNLib.Net.Http.Core.CoreBufferHelpers;
-
namespace VNLib.Net.Http.Core
{
internal static class HttpRequestExtensions
@@ -92,6 +91,7 @@ namespace VNLib.Net.Http.Core
&& (!Request.Origin.Authority.Equals(Request.Location.Authority, StringComparison.Ordinal)
|| !Request.Origin.Scheme.Equals(Request.Location.Scheme, StringComparison.Ordinal));
}
+
/// <summary>
/// Is the current connection a websocket upgrade request handshake
/// </summary>
@@ -99,11 +99,13 @@ namespace VNLib.Net.Http.Core
public static bool IsWebSocketRequest(this HttpRequest Request)
{
string? upgrade = Request.Headers[HttpRequestHeader.Upgrade];
+
if (!string.IsNullOrWhiteSpace(upgrade) && upgrade.Contains("websocket", StringComparison.OrdinalIgnoreCase))
{
//This request is a websocket request
//Check connection header
string? connection = Request.Headers[HttpRequestHeader.Connection];
+
//Must be a web socket request
return !string.IsNullOrWhiteSpace(connection) && connection.Contains("upgrade", StringComparison.OrdinalIgnoreCase);
}
@@ -130,175 +132,295 @@ namespace VNLib.Net.Http.Core
/// <summary>
/// Initializes the <see cref="HttpRequest.RequestBody"/> for the current request
/// </summary>
- /// <param name="Request"></param>
- /// <param name="maxBufferSize">The maxium buffer size allowed while parsing reqeust body data</param>
- /// <param name="encoding">The request data encoding for url encoded or form data bodies</param>
+ /// <param name="context"></param>
/// <exception cref="IOException"></exception>
/// <exception cref="OverflowException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
- internal static ValueTask InitRequestBodyAsync(this HttpRequest Request, int maxBufferSize, Encoding encoding)
+ internal static ValueTask InitRequestBodyAsync(this HttpContext context)
{
- /*
- * Parses query parameters from the request location query
- */
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- static void ParseQueryArgs(HttpRequest Request)
+ //Parse query
+ ParseQueryArgs(context.Request);
+
+ //Decode requests from body
+ return !context.Request.HasEntityBody ? ValueTask.CompletedTask : ParseInputStream(context);
+ }
+
+ private static async ValueTask ParseInputStream(HttpContext context)
+ {
+ HttpRequest request = context.Request;
+ IHttpContextInformation info = context;
+ IHttpMemoryPool pool = context.ParentServer.Config.MemoryPool;
+
+ //Gets the max form data buffer size to help calculate the initial char buffer size
+ int maxBufferSize = context.ParentServer.Config.BufferConfig.FormDataBufferSize;
+
+ switch (request.ContentType)
+ {
+ //CT not supported, dont read it
+ case ContentType.NonSupported:
+ break;
+ case ContentType.UrlEncoded:
+ {
+ //Calculate a largest available buffer to read the entire stream or up to the maximum buffer size
+ int bufferSize = (int)Math.Min(request.InputStream.Length, maxBufferSize);
+
+ //Alloc the form data character buffer, this will need to grow if the form data is larger than the buffer
+ using MemoryHandle<char> urlbody = pool.AllocFormDataBuffer<char>(bufferSize);
+
+ //Get a buffer for the form data
+ Memory<byte> formBuffer = context.Buffers.GetFormDataBuffer();
+
+ //Load char buffer from stream
+ int chars = await BufferInputStream(request.InputStream, urlbody, formBuffer, info.Encoding);
+
+ //Get the body as a span, and split the 'string' at the & character
+ ((ReadOnlySpan<char>)urlbody.AsSpan(0, chars))
+ .Split('&', StringSplitOptions.RemoveEmptyEntries, UrlEncodedSplitCb, request);
+
+ }
+ break;
+ case ContentType.MultiPart:
+ {
+ //Make sure we have a boundry specified
+ if (string.IsNullOrWhiteSpace(request.Boundry))
+ {
+ break;
+ }
+
+ //Calculate a largest available buffer to read the entire stream or up to the maximum buffer size
+ int bufferSize = (int)Math.Min(request.InputStream.Length, maxBufferSize);
+
+ //Alloc the form data buffer
+ using MemoryHandle<char> formBody = pool.AllocFormDataBuffer<char>(bufferSize);
+
+ //Get a buffer for the form data
+ Memory<byte> formBuffer = context.Buffers.GetFormDataBuffer();
+
+ //Load char buffer from stream
+ int chars = await BufferInputStream(request.InputStream, formBody, formBuffer, info.Encoding);
+
+ //Split the body as a span at the boundries
+ ((ReadOnlySpan<char>)formBody.AsSpan(0, chars))
+ .Split($"--{request.Boundry}", StringSplitOptions.RemoveEmptyEntries, FormDataBodySplitCb, context);
+
+ }
+ break;
+ //Default case is store as a file
+ default:
+ //add upload
+ request.RequestBody.Uploads.Add(new(request.InputStream, false, request.ContentType, null));
+ break;
+ }
+ }
+
+ /*
+ * Reads the input stream into the char buffer and returns the number of characters read. This method
+ * expands the char buffer as needed to accomodate the input stream.
+ *
+ * We assume the parsing method checked the size of the input stream so we can assume its safe to read
+ * all of it into memory.
+ */
+ private static async ValueTask<int> BufferInputStream(Stream stream, MemoryHandle<char> charBuffer, Memory<byte> binBuffer, Encoding encoding)
+ {
+ int length = 0;
+ do
{
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- //Query string parse method
- static void QueryParser(ReadOnlySpan<char> queryArgument, HttpRequest Request)
+ //read async
+ int read = await stream.ReadAsync(binBuffer);
+
+ //guard
+ if (read <= 0)
{
- //Split spans after the '=' character
- ReadOnlySpan<char> key = queryArgument.SliceBeforeParam('=');
- ReadOnlySpan<char> value = queryArgument.SliceAfterParam('=');
- //Insert into dict
- Request.RequestBody.QueryArgs[key.ToString()] = value.ToString();
+ break;
}
- //if the request has query args, parse and store them
- ReadOnlySpan<char> queryString = Request.Location.Query;
- if (!queryString.IsEmpty)
+ //calculate the number of characters
+ int numChars = encoding.GetCharCount(binBuffer.Span[..read]);
+
+ //Guard for overflow
+ if (((ulong)(numChars + length)) >= int.MaxValue)
{
- //trim leading '?' if set
- queryString = queryString.TrimStart('?');
- //Split args by '&'
- queryString.Split('&', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, QueryParser, Request);
+ throw new OverflowException();
}
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- static async ValueTask ParseInputStream(HttpRequest Request, int maxBufferSize, Encoding encoding)
+ //Re-alloc buffer
+ charBuffer.ResizeIfSmaller(length + numChars);
+
+ //Decode and update position
+ _ = encoding.GetChars(binBuffer.Span[..read], charBuffer.Span.Slice(length, numChars));
+
+ //Update char count
+ length += numChars;
+
+ } while (true);
+
+ //Return the number of characters read
+ return length;
+ }
+
+ /*
+ * Parses a Form-Data content type request entity body and stores those arguments in
+ * Request uploads or request args
+ */
+ private static void FormDataBodySplitCb(ReadOnlySpan<char> formSegment, HttpContext state)
+ {
+ //Form data arguments
+ string? DispType = null, Name = null, FileName = null;
+
+ ContentType ctHeaderVal = ContentType.NonSupported;
+
+ //Get sliding window for parsing data
+ ForwardOnlyReader<char> reader = new(formSegment.TrimCRLF());
+
+ //Read content headers
+ do
{
- /*
- * Reads all available data from the request input stream
- * If the stream size is smaller than a TCP buffer size, will complete synchronously
- */
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- static ValueTask<VnString> ReadInputStreamAsync(HttpRequest Request, int maxBufferSize, Encoding encoding)
+ //Get the index of the next crlf
+ int index = reader.Window.IndexOf(HttpHelpers.CRLF);
+
+ //end of headers
+ if (index < 1)
{
- //Calculate a largest available buffer to read the entire stream or up to the maximum buffer size
- int bufferSize = (int)Math.Min(Request.InputStream.Length, maxBufferSize);
- //Read the stream into a vnstring
- return VnString.FromStreamAsync(Request.InputStream, encoding, HttpPrivateHeap, bufferSize);
+ break;
}
- /*
- * SpanSplit callback function for storing UrlEncoded request entity
- * bodies in key-value pairs and writing them to the
- */
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- static void UrlEncodedSplitCb(ReadOnlySpan<char> kvArg, HttpRequest Request)
+
+ //Get header data
+ ReadOnlySpan<char> header = reader.Window[..index];
+
+ //Split header at colon
+ int colon = header.IndexOf(':');
+
+ //If no data is available after the colon the header is not valid, so move on to the next body
+ if (colon < 1)
{
- //Get key side of agument (or entire argument if no value is set)
- ReadOnlySpan<char> key = kvArg.SliceBeforeParam('=');
- ReadOnlySpan<char> value = kvArg.SliceAfterParam('=');
- //trim, allocate strings, and store in the request arg dict
- Request.RequestBody.RequestArgs[key.TrimCRLF().ToString()] = value.TrimCRLF().ToString();
+ return;
}
- /*
- * Parses a Form-Data content type request entity body and stores those arguments in
- * Request uploads or request args
- */
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- static void FormDataBodySplitCb(ReadOnlySpan<char> formSegment, ValueTuple<HttpRequestBody, Encoding> state)
+ //Hash the header value into a header enum
+ HttpRequestHeader headerType = HttpHelpers.GetRequestHeaderEnumFromValue(header[..colon]);
+
+ //get the header value
+ ReadOnlySpan<char> headerValue = header[(colon + 1)..];
+
+ //Check for content dispositon header
+ if (headerType == HttpHelpers.ContentDisposition)
{
- //Form data arguments
- string? DispType = null, Name = null, FileName = null;
- ContentType ctHeaderVal = ContentType.NonSupported;
- //Get sliding window for parsing data
- ForwardOnlyReader<char> reader = new(formSegment.TrimCRLF());
- //Read content headers
- do
- {
- //Get the index of the next crlf
- int index = reader.Window.IndexOf(HttpHelpers.CRLF);
- //end of headers
- if (index < 1)
- {
- break;
- }
- //Get header data
- ReadOnlySpan<char> header = reader.Window[..index];
- //Split header at colon
- int colon = header.IndexOf(':');
- //If no data is available after the colon the header is not valid, so move on to the next body
- if (colon < 1)
- {
- return;
- }
- //Hash the header value into a header enum
- HttpRequestHeader headerType = HttpHelpers.GetRequestHeaderEnumFromValue(header[..colon]);
- //get the header value
- ReadOnlySpan<char> headerValue = header[(colon + 1)..];
- //Check for content dispositon header
- if (headerType == HttpHelpers.ContentDisposition)
- {
- //Parse the content dispostion
- HttpHelpers.ParseDisposition(headerValue, out DispType, out Name, out FileName);
- }
- //Check for content type
- else if (headerType == HttpRequestHeader.ContentType)
- {
- //The header value for content type should be an MIME content type
- ctHeaderVal = HttpHelpers.GetContentType(headerValue.Trim().ToString());
- }
- //Shift window to the next line
- reader.Advance(index + HttpHelpers.CRLF.Length);
- } while (true);
- //Remaining data should be the body data (will have leading and trailing CRLF characters
- //If filename is set, this must be a file
- if (!string.IsNullOrWhiteSpace(FileName))
- {
- //Store the file in the uploads
- state.Item1.Uploads.Add(FileUpload.FromString(reader.Window.TrimCRLF(), state.Item2, FileName, ctHeaderVal));
- }
- //Make sure the name parameter was set and store the message body as a string
- else if (!string.IsNullOrWhiteSpace(Name))
- {
- //String data as body
- state.Item1.RequestArgs[Name] = reader.Window.TrimCRLF().ToString();
- }
+ //Parse the content dispostion
+ HttpHelpers.ParseDisposition(headerValue, out DispType, out Name, out FileName);
}
-
- switch (Request.ContentType)
+ //Check for content type
+ else if (headerType == HttpRequestHeader.ContentType)
{
- //CT not supported, dont read it
- case ContentType.NonSupported:
- break;
- case ContentType.UrlEncoded:
- //Create a vnstring from the message body and parse it (assuming url encoded bodies are small so a small stack buffer will be fine)
- using (VnString urlbody = await ReadInputStreamAsync(Request, maxBufferSize, encoding))
- {
- //Get the body as a span, and split the 'string' at the & character
- urlbody.AsSpan().Split('&', StringSplitOptions.RemoveEmptyEntries, UrlEncodedSplitCb, Request);
- }
- break;
- case ContentType.MultiPart:
- //Make sure we have a boundry specified
- if (string.IsNullOrWhiteSpace(Request.Boundry))
- {
- break;
- }
- //Read all data from stream into string
- using (VnString body = await ReadInputStreamAsync(Request, maxBufferSize, encoding))
- {
- //Split the body as a span at the boundries
- body.AsSpan().Split($"--{Request.Boundry}", StringSplitOptions.RemoveEmptyEntries, FormDataBodySplitCb, (Request.RequestBody, encoding));
- }
- break;
- //Default case is store as a file
- default:
- //add upload
- Request.RequestBody.Uploads.Add(new(Request.InputStream, string.Empty, Request.ContentType, false));
- break;
+ //The header value for content type should be an MIME content type
+ ctHeaderVal = HttpHelpers.GetContentType(headerValue.Trim().ToString());
}
+
+ //Shift window to the next line
+ reader.Advance(index + HttpHelpers.CRLF.Length);
+
+ } while (true);
+
+ //Remaining data should be the body data (will have leading and trailing CRLF characters
+ //If filename is set, this must be a file
+ if (!string.IsNullOrWhiteSpace(FileName))
+ {
+ ReadOnlySpan<char> fileData = reader.Window.TrimCRLF();
+
+ FileUpload upload = UploadFromString(fileData, state, FileName, ctHeaderVal);
+
+ //Store the file in the uploads
+ state.Request.RequestBody.Uploads.Add(upload);
}
- //Parse query
- ParseQueryArgs(Request);
+ //Make sure the name parameter was set and store the message body as a string
+ else if (!string.IsNullOrWhiteSpace(Name))
+ {
+ //String data as body
+ state.Request.RequestBody.RequestArgs[Name] = reader.Window.TrimCRLF().ToString();
+ }
+ }
- //Decode requests from body
- return !Request.HasEntityBody ? ValueTask.CompletedTask : ParseInputStream(Request, maxBufferSize, encoding);
+ /// <summary>
+ /// Allocates a new binary buffer, encodes, and copies the specified data to a new <see cref="FileUpload"/>
+ /// structure of the specified content type
+ /// </summary>
+ /// <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>
+ /// <returns>The <see cref="FileUpload"/> container</returns>
+ private static FileUpload UploadFromString(ReadOnlySpan<char> data, HttpContext context, string filename, ContentType ct)
+ {
+ IHttpContextInformation info = context;
+ IHttpMemoryPool pool = context.ParentServer.Config.MemoryPool;
+
+ //get number of bytes
+ int bytes = info.Encoding.GetByteCount(data);
+
+ //get a buffer from the HTTP heap
+ MemoryHandle<byte> buffHandle = pool.AllocFormDataBuffer<byte>(bytes);
+ try
+ {
+ //Convert back to binary
+ bytes = info.Encoding.GetBytes(data, buffHandle);
+
+ //Create a new memory stream encapsulating the file data
+ VnMemoryStream vms = VnMemoryStream.ConsumeHandle(buffHandle, bytes, true);
+
+ //Create new upload wrapper that owns the stream
+ return new(vms, true, ct, filename);
+ }
+ catch
+ {
+ //Make sure the hanle gets disposed if there is an error
+ buffHandle.Dispose();
+ throw;
+ }
+ }
+
+ /*
+ * SpanSplit callback function for storing UrlEncoded request entity
+ * bodies in key-value pairs and writing them to the
+ */
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void UrlEncodedSplitCb(ReadOnlySpan<char> kvArg, HttpRequest Request)
+ {
+ //Get key side of agument (or entire argument if no value is set)
+ ReadOnlySpan<char> key = kvArg.SliceBeforeParam('=');
+ ReadOnlySpan<char> value = kvArg.SliceAfterParam('=');
+
+ //trim, allocate strings, and store in the request arg dict
+ Request.RequestBody.RequestArgs[key.TrimCRLF().ToString()] = value.TrimCRLF().ToString();
+ }
+
+ /*
+ * Parses query parameters from the request location query
+ */
+ private static void ParseQueryArgs(HttpRequest Request)
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ //Query string parse method
+ static void QueryParser(ReadOnlySpan<char> queryArgument, HttpRequest Request)
+ {
+ //Split spans at the '=' character
+ ReadOnlySpan<char> key = queryArgument.SliceBeforeParam('=');
+ ReadOnlySpan<char> value = queryArgument.SliceAfterParam('=');
+
+ //Insert into dict
+ Request.RequestBody.QueryArgs[key.ToString()] = value.ToString();
+ }
+
+ //if the request has query args, parse and store them
+ ReadOnlySpan<char> queryString = Request.Location.Query;
+
+ if (!queryString.IsEmpty)
+ {
+ //trim leading '?' if set
+ queryString = queryString.TrimStart('?');
+
+ //Split args by '&'
+ queryString.Split('&', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, QueryParser, Request);
+ }
}
}
} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs b/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
index f633a4a..f7e17f7 100644
--- a/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
+++ b/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
@@ -62,7 +62,7 @@ namespace VNLib.Net.Http.Core
/// <returns>0 if the request line was successfully parsed, a status code if the request could not be processed</returns>
/// <exception cref="UriFormatException"></exception>
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
- public static HttpStatusCode Http1ParseRequestLine(this HttpRequest Request, ref Http1ParseState parseState, ref TransportReader reader, in Span<char> lineBuf)
+ public static HttpStatusCode Http1ParseRequestLine(this HttpRequest Request, ref Http1ParseState parseState, ref TransportReader reader, Span<char> lineBuf)
{
//Locals
ERRNO requestResult;
@@ -70,6 +70,7 @@ namespace VNLib.Net.Http.Core
//Read the start line
requestResult = reader.ReadLine(lineBuf);
+
//Must be able to parse the verb and location
if (requestResult < 1)
{
@@ -79,6 +80,7 @@ namespace VNLib.Net.Http.Core
//true up the request line to actual size
ReadOnlySpan<char> requestLine = lineBuf[..(int)requestResult].Trim();
+
//Find the first white space character ("GET / HTTP/1.1")
index = requestLine.IndexOf(' ');
if (index == -1)
@@ -88,6 +90,7 @@ namespace VNLib.Net.Http.Core
//Decode the verb (function requires the string be the exact characters of the request method)
Request.Method = HttpHelpers.GetRequestMethod(requestLine[0..index]);
+
//Make sure the method is supported
if (Request.Method == HttpMethod.None)
{
@@ -96,6 +99,7 @@ namespace VNLib.Net.Http.Core
//location string should be from end of verb to HTTP/ NOTE: Only supports http... this is an http server
endloc = requestLine.LastIndexOf(" HTTP/", StringComparison.OrdinalIgnoreCase);
+
//Client must specify an http version prepended by a single whitespace(rfc2612)
if (endloc == -1)
{
@@ -134,8 +138,10 @@ namespace VNLib.Net.Http.Core
//Set a default scheme
Scheme = Request.EncryptionVersion == SslProtocols.None ? Uri.UriSchemeHttp : Uri.UriSchemeHttps,
};
+
//Need to manually parse the query string
int q = paq.IndexOf('?');
+
//has query?
if (q == -1)
{
@@ -164,7 +170,7 @@ namespace VNLib.Net.Http.Core
/// <param name="lineBuf">The buffer read data from the transport with</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 Http1ParseHeaders(this HttpRequest Request, ref Http1ParseState parseState, ref TransportReader reader, in HttpConfig Config, in Span<char> lineBuf)
+ public static HttpStatusCode Http1ParseHeaders(this HttpRequest Request, ref Http1ParseState parseState, ref TransportReader reader, in HttpConfig Config, Span<char> lineBuf)
{
try
{
@@ -253,6 +259,7 @@ namespace VNLib.Net.Http.Core
{
//Update keepalive, if the connection header contains "closed" and with the current value of keepalive
Request.KeepAlive &= !requestHeaderValue.Contains("close", StringComparison.OrdinalIgnoreCase);
+
//Also store the connecion header into the store
Request.Headers.Add(HttpRequestHeader.Connection, requestHeaderValue.ToString());
}
@@ -264,8 +271,10 @@ namespace VNLib.Net.Http.Core
//Invalid content type header value
return HttpStatusCode.UnsupportedMediaType;
}
+
Request.Boundry = boundry;
Request.Charset = charset;
+
//Get the content type enum from mime type
Request.ContentType = HttpHelpers.GetContentType(ct);
}
@@ -303,6 +312,7 @@ namespace VNLib.Net.Http.Core
//Split the host value by the port parameter
ReadOnlySpan<char> port = requestHeaderValue.SliceAfterParam(':').Trim();
+
//Slicing beofre the colon should always provide a useable hostname, so allocate a string for it
string host = requestHeaderValue.SliceBeforeParam(':').Trim().ToString();
@@ -344,8 +354,8 @@ namespace VNLib.Net.Http.Core
{
//Get the name parameter and alloc a string
string name = cookie.SliceBeforeParam('=').Trim().ToString();
- //Get the value parameter and alloc a string
string value = cookie.SliceAfterParam('=').Trim().ToString();
+
//Add the cookie to the dictionary
_ = cookieContainer.TryAdd(name, value);
}
@@ -374,15 +384,19 @@ namespace VNLib.Net.Http.Core
{
//See if range bytes value has been set
ReadOnlySpan<char> rawRange = requestHeaderValue.SliceAfterParam("bytes=").TrimCRLF();
+
//Make sure the bytes parameter is set
if (rawRange.IsEmpty)
{
break;
}
+
//Get start range
ReadOnlySpan<char> startRange = rawRange.SliceBeforeParam('-');
+
//Get end range (empty if no - exists)
ReadOnlySpan<char> endRange = rawRange.SliceAfterParam('-');
+
//See if a range end is specified
if (endRange.IsEmpty)
{
@@ -413,6 +427,7 @@ namespace VNLib.Net.Http.Core
{
//Alloc a string for origin
string origin = requestHeaderValue.ToString();
+
//Origin headers should always be absolute address "parsable"
if (Uri.TryCreate(origin, UriKind.Absolute, out Uri? org))
{
@@ -495,6 +510,7 @@ namespace VNLib.Net.Http.Core
//Check for chuncked transfer encoding
ReadOnlySpan<char> transfer = Request.Headers[HttpRequestHeader.TransferEncoding];
+
if (!transfer.IsEmpty && transfer.Contains("chunked", StringComparison.OrdinalIgnoreCase))
{
//Not a valid http version for chunked transfer encoding
@@ -502,6 +518,7 @@ namespace VNLib.Net.Http.Core
{
return HttpStatusCode.BadRequest;
}
+
/*
* Was a content length also specified?
* This is an issue and is likely an attack. I am choosing not to support
diff --git a/lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs b/lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs
index 35c0275..9ea06f3 100644
--- a/lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs
+++ b/lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -23,15 +23,10 @@
*/
using System;
-using System.IO;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
using VNLib.Utils;
using VNLib.Utils.IO;
-
-using static VNLib.Net.Http.Core.CoreBufferHelpers;
+using VNLib.Net.Http.Core.Buffering;
namespace VNLib.Net.Http.Core
{
@@ -41,36 +36,53 @@ namespace VNLib.Net.Http.Core
/// </summary>
internal class ChunkDataAccumulator : IDataAccumulator<byte>, IHttpLifeCycle
{
+ private const string LAST_CHUNK_STRING = "0\r\n\r\n";
public const int RESERVED_CHUNK_SUGGESTION = 32;
-
- private readonly int BufferSize;
+
private readonly int ReservedSize;
- private readonly Encoding Encoding;
- private readonly ReadOnlyMemory<byte> CRLFBytes;
+ private readonly IHttpContextInformation Context;
+ private readonly IChunkAccumulatorBuffer Buffer;
+ private readonly ReadOnlyMemory<byte> LastChunk;
- public ChunkDataAccumulator(Encoding encoding, int bufferSize)
+ public ChunkDataAccumulator(IChunkAccumulatorBuffer buffer, IHttpContextInformation context)
{
- Encoding = encoding;
- CRLFBytes = encoding.GetBytes(HttpHelpers.CRLF);
-
ReservedSize = RESERVED_CHUNK_SUGGESTION;
- BufferSize = bufferSize;
- }
- private byte[]? _buffer;
+ Context = context;
+ Buffer = buffer;
+
+ //Convert and store cached versions of the last chunk bytes
+ LastChunk = context.Encoding.GetBytes(LAST_CHUNK_STRING);
+ }
+
+ /*
+ * Reserved offset is a pointer to the first byte of the reserved chunk window
+ * that actually contains the size segment data.
+ */
+
private int _reservedOffset;
///<inheritdoc/>
- public int RemainingSize => _buffer!.Length - AccumulatedSize;
+ public int RemainingSize => Buffer.Size - AccumulatedSize;
+
///<inheritdoc/>
- public Span<byte> Remaining => _buffer!.AsSpan(AccumulatedSize);
+ public Span<byte> Remaining => Buffer.GetBinSpan()[AccumulatedSize..];
+
///<inheritdoc/>
- public Span<byte> Accumulated => _buffer!.AsSpan(_reservedOffset, AccumulatedSize);
+ public Span<byte> Accumulated => Buffer.GetBinSpan()[_reservedOffset.. AccumulatedSize];
+
///<inheritdoc/>
public int AccumulatedSize { get; set; }
- private Memory<byte> CompleteChunk => _buffer.AsMemory(_reservedOffset, (AccumulatedSize - _reservedOffset));
+ /*
+ * Completed chunk is the segment of the buffer that contains the size segment
+ * followed by the accumulated chunk data, and the trailing crlf.
+ *
+ * AccumulatedSize points to the end of the accumulated chunk data. The reserved
+ * offset points to the start of the size segment.
+ */
+ private Memory<byte> GetCompleteChunk() => Buffer.GetMemory()[_reservedOffset..AccumulatedSize];
/// <summary>
/// Attempts to buffer as much data as possible from the specified data
@@ -80,10 +92,11 @@ namespace VNLib.Net.Http.Core
public ERRNO TryBufferChunk(ReadOnlySpan<byte> data)
{
//Calc data size and reserve space for final crlf
- int dataToCopy = Math.Min(data.Length, RemainingSize - CRLFBytes.Length);
+ int dataToCopy = Math.Min(data.Length, RemainingSize - Context.CrlfBytes.Length);
//Write as much data as possible
data[..dataToCopy].CopyTo(Remaining);
+
//Advance buffer
Advance(dataToCopy);
@@ -96,7 +109,7 @@ namespace VNLib.Net.Http.Core
private void InitReserved()
{
- //First reserve the chunk window by advancing the accumulator to the size
+ //First reserve the chunk window by advancing the accumulator to the reserved size
Advance(ReservedSize);
}
@@ -111,48 +124,59 @@ namespace VNLib.Net.Http.Core
}
/// <summary>
- /// Writes the buffered data as a single chunk to the stream asynchronously. The internal
- /// state is reset if writing compleded successfully
+ /// Complets and returns the memory segment containing the chunk data to send
+ /// to the client. This also resets the accumulator.
/// </summary>
- /// <param name="output">The stream to write data to</param>
- /// <param name="cancellation">A token to cancel the operation</param>
- /// <returns>A value task that resolves when the data has been written to the stream</returns>
- public async ValueTask FlushAsync(Stream output, CancellationToken cancellation)
+ /// <returns></returns>
+ public Memory<byte> GetChunkData()
{
//Update the chunk size
UpdateChunkSize();
//Write trailing chunk delimiter
- this.Append(CRLFBytes.Span);
-
- //write to stream
- await output.WriteAsync(CompleteChunk, cancellation);
+ this.Append(Context.CrlfBytes.Span);
- //Reset for next chunk
- Reset();
+ return GetCompleteChunk();
}
/// <summary>
- /// Writes the buffered data as a single chunk to the stream. The internal
- /// state is reset if writing compleded successfully
+ /// Complets and returns the memory segment containing the chunk data to send
+ /// to the client.
/// </summary>
- /// <param name="output">The stream to write data to</param>
- /// <returns>A value task that resolves when the data has been written to the stream</returns>
- public void Flush(Stream output)
+ /// <returns></returns>
+ public Memory<byte> GetFinalChunkData()
{
//Update the chunk size
UpdateChunkSize();
//Write trailing chunk delimiter
- this.Append(CRLFBytes.Span);
+ this.Append(Context.CrlfBytes.Span);
- //write to stream
- output.Write(CompleteChunk.Span);
+ //Write final chunk to the end of the accumulator
+ this.Append(LastChunk.Span);
- //Reset for next chunk
- Reset();
+ return GetCompleteChunk();
}
+
+ /*
+ * UpdateChunkSize method updates the running total of the chunk size
+ * in the reserved segment of the buffer. This is because http chunking
+ * requires hex encoded chunk sizes to be written as the first bytes of
+ * the chunk. So when the flush methods are called, the chunk size
+ * at the beginning of the chunk is updated to reflect the total size.
+ *
+ * Because we need to store space at the head of the chunk for the size
+ * we need to reserve space for the size segment.
+ *
+ * The size sigment bytes abutt the chunk data bytes, so the size segment
+ * is stored at the end of the reserved segment, which is directly before
+ * the start of the chunk data.
+ *
+ * [reserved segment] [chunk data] [eoc]
+ * [...0a \r\n] [10 bytes of data] [eoc]
+ */
+
private void UpdateChunkSize()
{
const int CharBufSize = 2 * sizeof(int);
@@ -173,7 +197,7 @@ namespace VNLib.Net.Http.Core
//temp buffer to store encoded data in
Span<byte> encBuf = stackalloc byte[ReservedSize];
//Encode the chunk size chars
- int initOffset = Encoding.GetBytes(s[..written], encBuf);
+ int initOffset = Context.Encoding.GetBytes(s[..written], encBuf);
Span<byte> encoded = encBuf[..initOffset];
@@ -185,9 +209,9 @@ namespace VNLib.Net.Http.Core
* the exact size required to store the encoded chunk size
*/
- _reservedOffset = (ReservedSize - (initOffset + CRLFBytes.Length));
+ _reservedOffset = (ReservedSize - (initOffset + Context.CrlfBytes.Length));
- Span<byte> upshifted = _buffer!.AsSpan(_reservedOffset, ReservedSize);
+ Span<byte> upshifted = Buffer.GetBinSpan()[_reservedOffset..ReservedSize];
//First write the chunk size
encoded.CopyTo(upshifted);
@@ -196,7 +220,7 @@ namespace VNLib.Net.Http.Core
upshifted = upshifted[initOffset..];
//Copy crlf
- CRLFBytes.Span.CopyTo(upshifted);
+ Context.CrlfBytes.Span.CopyTo(upshifted);
}
@@ -213,16 +237,10 @@ namespace VNLib.Net.Http.Core
}
public void OnPrepare()
- {
- //Alloc buffer
- _buffer = HttpBinBufferPool.Rent(BufferSize);
- }
+ { }
public void OnRelease()
- {
- HttpBinBufferPool.Return(_buffer!);
- _buffer = null;
- }
+ { }
}
} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Response/ChunkedStream.cs b/lib/Net.Http/src/Core/Response/ChunkedStream.cs
index 724e28d..1b4f7de 100644
--- a/lib/Net.Http/src/Core/Response/ChunkedStream.cs
+++ b/lib/Net.Http/src/Core/Response/ChunkedStream.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -36,12 +36,12 @@
using System;
using System.IO;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
using VNLib.Utils;
using VNLib.Utils.Memory;
+using VNLib.Net.Http.Core.Buffering;
#pragma warning disable CA2215 // Dispose methods should call base class dispose
@@ -55,27 +55,19 @@ namespace VNLib.Net.Http.Core
/// </summary>
private sealed class ChunkedStream : Stream, IHttpLifeCycle
{
- private const string LAST_CHUNK_STRING = "0\r\n\r\n";
-
- private readonly ReadOnlyMemory<byte> LastChunk;
+
private readonly ChunkDataAccumulator ChunckAccumulator;
- private readonly Func<Stream> GetTransport;
+ private readonly IHttpContextInformation ContextInfo;
private Stream? TransportStream;
private bool HadError;
- internal ChunkedStream(Encoding encoding, int chunkBufferSize, Func<Stream> getStream)
+ internal ChunkedStream(IChunkAccumulatorBuffer buffer, IHttpContextInformation context)
{
- //Convert and store cached versions of the last chunk bytes
- LastChunk = encoding.GetBytes(LAST_CHUNK_STRING);
-
- //get the min buffer by rounding to the nearest page
- int actualBufSize = (int)MemoryUtil.NearestPage(chunkBufferSize);
+ ContextInfo = context;
//Init accumulator
- ChunckAccumulator = new(encoding, actualBufSize);
-
- GetTransport = getStream;
+ ChunckAccumulator = new(buffer, context);
}
@@ -91,7 +83,7 @@ namespace VNLib.Net.Http.Core
public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
- public override void Write(byte[] buffer, int offset, int count) => Write(new ReadOnlySpan<byte>(buffer, offset, count));
+ public override void Write(byte[] buffer, int offset, int count) => Write(buffer.AsSpan(offset, count));
public override void Write(ReadOnlySpan<byte> chunk)
{
//Only write non-zero chunks
@@ -116,7 +108,14 @@ namespace VNLib.Net.Http.Core
reader.Advance(written);
//Flush accumulator
- ChunckAccumulator.Flush(TransportStream!);
+ Memory<byte> accChunk = ChunckAccumulator.GetChunkData();
+
+ //Reset the chunk accumulator
+ ChunckAccumulator.Reset();
+
+ //Write chunk data
+ TransportStream!.Write(accChunk.Span);
+
//Continue to buffer / flush as needed
continue;
}
@@ -161,11 +160,19 @@ namespace VNLib.Net.Http.Core
//Advance reader
reader.Advance(written);
+ //Flush accumulator
+ Memory<byte> accChunk = ChunckAccumulator.GetChunkData();
+
+ //Reset the chunk accumulator
+ ChunckAccumulator.Reset();
+
//Flush accumulator async
- await ChunckAccumulator.FlushAsync(TransportStream!, cancellationToken);
+ await TransportStream!.WriteAsync(accChunk, cancellationToken);
+
//Continue to buffer / flush as needed
continue;
}
+
break;
}
while (true);
@@ -185,12 +192,15 @@ namespace VNLib.Net.Http.Core
{
return;
}
+
+ //Complete the last chunk
+ Memory<byte> chunkData = ChunckAccumulator.GetFinalChunkData();
+
+ //Reset the accumulator
+ ChunckAccumulator.Reset();
//Write remaining data to stream
- await ChunckAccumulator.FlushAsync(TransportStream!, CancellationToken.None);
-
- //Write final chunk
- await TransportStream!.WriteAsync(LastChunk, CancellationToken.None);
+ await TransportStream!.WriteAsync(chunkData, CancellationToken.None);
//Flush base stream
await TransportStream!.FlushAsync(CancellationToken.None);
@@ -205,12 +215,15 @@ namespace VNLib.Net.Http.Core
{
return;
}
-
- //Write remaining data to stream
- ChunckAccumulator.Flush(TransportStream!);
- //Write final chunk
- TransportStream!.Write(LastChunk.Span);
+ //Complete the last chunk
+ Memory<byte> chunkData = ChunckAccumulator.GetFinalChunkData();
+
+ //Reset the accumulator
+ ChunckAccumulator.Reset();
+
+ //Write chunk data
+ TransportStream!.Write(chunkData.Span);
//Flush base stream
TransportStream!.Flush();
@@ -234,7 +247,7 @@ namespace VNLib.Net.Http.Core
ChunckAccumulator.OnNewRequest();
//Get transport stream even if not used
- TransportStream = GetTransport();
+ TransportStream = ContextInfo.GetTransport();
}
public void OnComplete()
diff --git a/lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs b/lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs
index c43441c..8c2461c 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) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -23,135 +23,102 @@
*/
using System;
-using System.IO;
-using System.Text;
-using System.Runtime.InteropServices;
-using VNLib.Utils;
-using VNLib.Utils.IO;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
-using static VNLib.Net.Http.Core.CoreBufferHelpers;
+using VNLib.Net.Http.Core.Buffering;
namespace VNLib.Net.Http.Core
{
+
internal partial class HttpResponse
{
-
/// <summary>
/// Specialized data accumulator for compiling response headers
/// </summary>
- private sealed class HeaderDataAccumulator : IDataAccumulator<char>, IStringSerializeable, IHttpLifeCycle
+ private sealed class HeaderDataAccumulator
{
- private readonly int BufferSize;
+ private readonly IResponseHeaderAccBuffer _buffer;
+ private readonly IHttpContextInformation _contextInfo;
+ private int AccumulatedSize;
- public HeaderDataAccumulator(int bufferSize)
+ public HeaderDataAccumulator(IResponseHeaderAccBuffer accBuffer, IHttpContextInformation ctx)
{
- //Calc correct char buffer size from bin buffer
- this.BufferSize = bufferSize * sizeof(char);
+ _buffer = accBuffer;
+ _contextInfo = ctx;
}
- /*
- * May be an issue but wanted to avoid alloc
- * if possible since this is a field in a ref
- * type
- */
-
- private UnsafeMemoryHandle<byte>? _handle;
-
- public void Advance(int count)
- {
- //Advance writer
- AccumulatedSize += count;
- }
-
- public void WriteLine() => this.Append(HttpHelpers.CRLF);
-
- public void WriteLine(ReadOnlySpan<char> data)
+ /// <summary>
+ /// Initializes a new <see cref="ForwardOnlyWriter{T}"/> for buffering character header data
+ /// </summary>
+ /// <returns>A <see cref="ForwardOnlyWriter{T}"/> for buffering character header data</returns>
+ public ForwardOnlyWriter<char> GetWriter()
{
- this.Append(data);
- WriteLine();
+ Span<char> chars = _buffer.GetCharSpan();
+ return new ForwardOnlyWriter<char>(chars);
}
- /*Use bin buffers and cast to char buffer*/
- private Span<char> Buffer => MemoryMarshal.Cast<byte, char>(_handle!.Value.Span);
-
- public int RemainingSize => Buffer.Length - AccumulatedSize;
- public Span<char> Remaining => Buffer[AccumulatedSize..];
- public Span<char> Accumulated => Buffer[..AccumulatedSize];
- public int AccumulatedSize { get; set; }
-
/// <summary>
- /// Encodes the buffered data and writes it to the stream,
- /// attemts to avoid further allocation where possible
+ /// Encodes and writes the contents of the <see cref="ForwardOnlyWriter{T}"/> to the internal accumulator
/// </summary>
- /// <param name="enc"></param>
- /// <param name="baseStream"></param>
- public void Flush(Encoding enc, Stream baseStream)
+ /// <param name="writer">The character buffer writer to commit data from</param>
+ public void CommitChars(ref ForwardOnlyWriter<char> writer)
{
- ReadOnlySpan<char> span = Accumulated;
- //Calc the size of the binary buffer
- int byteSize = enc.GetByteCount(span);
- //See if there is enough room in the current char buffer
- if (RemainingSize < (byteSize / sizeof(char)))
- {
- //We need to alloc a binary buffer to write data to
- using UnsafeMemoryHandle<byte> bin = GetBinBuffer(byteSize, false);
- //encode data
- int encoded = enc.GetBytes(span, bin.Span);
- //Write to stream
- baseStream.Write(bin.Span[..encoded]);
- }
- else
+ if (writer.Written == 0)
{
- //Get bin buffer by casting remaining accumulator buffer
- Span<byte> bin = MemoryMarshal.Cast<char, byte>(Remaining);
- //encode data
- int encoded = enc.GetBytes(span, bin);
- //Write to stream
- baseStream.Write(bin[..encoded]);
+ return;
}
- Reset();
- }
-
- public void Reset() => AccumulatedSize = 0;
-
+ //Write the entire token to the buffer
+ WriteToken(writer.AsSpan());
+ }
- public void OnPrepare()
+ /// <summary>
+ /// Encodes a single token and writes it directly to the internal accumulator
+ /// </summary>
+ /// <param name="chars">The character sequence to accumulate</param>
+ public void WriteToken(ReadOnlySpan<char> chars)
{
- //Alloc buffer
- _handle = GetBinBuffer(BufferSize, false);
- }
+ //Get remaining buffer
+ Span<byte> remaining = _buffer.GetBinSpan()[AccumulatedSize..];
- public void OnRelease()
+ //Commit all chars to the buffer
+ AccumulatedSize += _contextInfo.Encoding.GetBytes(chars, remaining);
+ }
+
+ /// <summary>
+ /// Writes the http termination sequence to the internal accumulator
+ /// </summary>
+ public void WriteTermination()
{
- _handle!.Value.Dispose();
- _handle = null;
+ //Write the http termination sequence
+ Span<byte> remaining = _buffer.GetBinSpan()[AccumulatedSize..];
+
+ _contextInfo.CrlfBytes.Span.CopyTo(remaining);
+
+ //Advance the accumulated window
+ AccumulatedSize += _contextInfo.CrlfBytes.Length;
}
- public void OnNewRequest()
- {}
+ /// <summary>
+ /// Resets the internal accumulator
+ /// </summary>
+ public void Reset() => AccumulatedSize = 0;
- public void OnComplete()
+ /// <summary>
+ /// Gets the accumulated response data as its memory buffer, and resets the internal accumulator
+ /// </summary>
+ /// <returns>The buffer segment containing the accumulated response data</returns>
+ public Memory<byte> GetResponseData()
{
- Reset();
- }
+ //get the current buffer as memory and return the accumulated segment
+ Memory<byte> accumulated = _buffer.GetMemory()[..AccumulatedSize];
+ //Reset the buffer
+ Reset();
- ///<inheritdoc/>
- public string Compile() => Accumulated.ToString();
- ///<inheritdoc/>
- public void Compile(ref ForwardOnlyWriter<char> writer) => writer.Append(Accumulated);
- ///<inheritdoc/>
- public ERRNO Compile(in Span<char> buffer)
- {
- ForwardOnlyWriter<char> writer = new(buffer);
- Compile(ref writer);
- return writer.Written;
+ return accumulated;
}
- ///<inheritdoc/>
- public override string ToString() => Compile();
}
}
} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs b/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs
index b03363e..6bc92ff 100644
--- a/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs
+++ b/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -23,13 +23,13 @@
*/
using System;
-using System.Buffers;
-using System.IO.Compression;
using System.IO;
+using System.IO.Compression;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
+
namespace VNLib.Net.Http.Core
{
@@ -43,7 +43,7 @@ namespace VNLib.Net.Http.Core
* and the release method will be called so the context can be reused
*/
- ValueTask discardTask = Request.InputStream.DiscardRemainingAsync(ParentServer.Config.DiscardBufferSize);
+ ValueTask discardTask = Request.InputStream.DiscardRemainingAsync(Buffers);
//See if discard is needed
if (ResponseBody.HasData)
@@ -99,7 +99,7 @@ namespace VNLib.Net.Http.Core
Response.Headers[HttpResponseHeader.ContentLength] = ResponseBody.Length.ToString();
}
- //We must send headers here so content length doesnt get overwritten
+ //We must send headers here so content length doesnt get overwritten, close will be called after this to flush to transport
Response.FlushHeaders();
}
else
@@ -125,7 +125,7 @@ namespace VNLib.Net.Http.Core
Response.SetContentRange(range.Item1, endRange, length);
//Get the raw output stream and set the length to the number of bytes
- outputStream = Response.GetStream(length);
+ outputStream = await Response.GetStreamAsync(length);
await WriteEntityDataAsync(outputStream, length, token);
}
@@ -150,8 +150,10 @@ namespace VNLib.Net.Http.Core
{
//Specify gzip encoding (using chunked encoding)
Response.Headers[HttpResponseHeader.ContentEncoding] = "gzip";
+
//get the chunked output stream
- Stream chunked = Response.GetStream();
+ Stream chunked = await Response.GetStreamAsync();
+
//Use chunked encoding and send data as its written
outputStream = new GZipStream(chunked, ParentServer.Config.CompressionLevel, false);
}
@@ -161,7 +163,7 @@ namespace VNLib.Net.Http.Core
//Specify gzip encoding (using chunked encoding)
Response.Headers[HttpResponseHeader.ContentEncoding] = "deflate";
//get the chunked output stream
- Stream chunked = Response.GetStream();
+ Stream chunked = await Response.GetStreamAsync();
//Use chunked encoding and send data as its written
outputStream = new DeflateStream(chunked, ParentServer.Config.CompressionLevel, false);
}
@@ -171,7 +173,7 @@ namespace VNLib.Net.Http.Core
//Specify Brotli encoding (using chunked encoding)
Response.Headers[HttpResponseHeader.ContentEncoding] = "br";
//get the chunked output stream
- Stream chunked = Response.GetStream();
+ Stream chunked = await Response.GetStreamAsync();
//Use chunked encoding and send data as its written
outputStream = new BrotliStream(chunked, ParentServer.Config.CompressionLevel, false);
}
@@ -180,7 +182,7 @@ namespace VNLib.Net.Http.Core
case HttpRequestExtensions.CompressionType.None:
default:
//Since we know how long the response will be, we can submit it now (see note above for same issues)
- outputStream = Response.GetStream(ResponseBody.Length);
+ outputStream = await Response.GetStreamAsync(ResponseBody.Length);
break;
}
@@ -197,14 +199,11 @@ namespace VNLib.Net.Http.Core
//Determine if buffer is required
if (ResponseBody.BufferRequired)
{
- //Calc a buffer size (always a safe cast since rbs is an integer)
- int bufferSize = (int)Math.Min((long)ParentServer.Config.ResponseBufferSize, ResponseBody.Length);
-
- //Alloc buffer, and dispose when completed
- using IMemoryOwner<byte> buffer = CoreBufferHelpers.GetMemory(bufferSize, false);
+ //Get response data buffer, may be smaller than suggested size
+ Memory<byte> buffer = Buffers.GetResponseDataBuffer();
//Write response
- await ResponseBody.WriteEntityAsync(outputStream, buffer.Memory, token);
+ await ResponseBody.WriteEntityAsync(outputStream, buffer, token);
}
//No buffer is required, write response directly
else
@@ -227,14 +226,11 @@ namespace VNLib.Net.Http.Core
//Determine if buffer is required
if (ResponseBody.BufferRequired)
{
- //Calc a buffer size (always a safe cast since rbs is an integer)
- int bufferSize = (int)Math.Min((long)ParentServer.Config.ResponseBufferSize, ResponseBody.Length);
-
- //Alloc buffer, and dispose when completed
- using IMemoryOwner<byte> buffer = CoreBufferHelpers.GetMemory(bufferSize, false);
+ //Get response data buffer, may be smaller than suggested size
+ Memory<byte> buffer = Buffers.GetResponseDataBuffer();
//Write response
- await ResponseBody.WriteEntityAsync(outputStream, length, buffer.Memory, token);
+ await ResponseBody.WriteEntityAsync(outputStream, length, buffer, token);
}
//No buffer is required, write response directly
else
diff --git a/lib/Net.Http/src/Core/Response/HttpResponse.cs b/lib/Net.Http/src/Core/Response/HttpResponse.cs
index 69ef2f1..9093006 100644
--- a/lib/Net.Http/src/Core/Response/HttpResponse.cs
+++ b/lib/Net.Http/src/Core/Response/HttpResponse.cs
@@ -25,25 +25,26 @@
using System;
using System.IO;
using System.Net;
-using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
+using VNLib.Utils;
using VNLib.Utils.IO;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+using VNLib.Net.Http.Core.Buffering;
namespace VNLib.Net.Http.Core
{
- internal partial class HttpResponse : IHttpLifeCycle
+ internal partial class HttpResponse : IHttpLifeCycle, IStringSerializeable
{
private readonly HashSet<HttpCookie> Cookies;
private readonly HeaderDataAccumulator Writer;
private readonly DirectStream ReusableDirectStream;
private readonly ChunkedStream ReusableChunkedStream;
- private readonly Func<Stream> _getStream;
- private readonly Encoding ResponseEncoding;
- private readonly Func<HttpVersion> GetVersion;
+ private readonly IHttpContextInformation ContextInfo;
private bool HeadersSent;
private bool HeadersBegun;
@@ -55,24 +56,22 @@ namespace VNLib.Net.Http.Core
/// </summary>
public VnWebHeaderCollection Headers { get; }
- public HttpResponse(Encoding encoding, int headerBufferSize, int chunkedBufferSize, Func<Stream> getStream, Func<HttpVersion> getVersion)
+ public HttpResponse(IHttpBufferManager manager, IHttpContextInformation ctx)
{
+ ContextInfo = ctx;
+
//Initialize a new header collection and a cookie jar
Headers = new();
Cookies = new();
- //Create a new reusable writer stream
- Writer = new(headerBufferSize);
- _getStream = getStream;
- ResponseEncoding = encoding;
+ //Create a new reusable writer stream
+ Writer = new(manager.ResponseHeaderBuffer, ctx);
//Create a new chunked stream
- ReusableChunkedStream = new(encoding, chunkedBufferSize, getStream);
+ ReusableChunkedStream = new(manager.ChunkAccumulatorBuffer, ctx);
ReusableDirectStream = new();
- GetVersion = getVersion;
}
-
/// <summary>
/// Sets the status code of the response
/// </summary>
@@ -101,18 +100,27 @@ namespace VNLib.Net.Http.Core
internal async Task SendEarly100ContinueAsync()
{
Check();
+
//Send a status message with the continue response status
- Writer.WriteLine(HttpHelpers.GetResponseString(GetVersion(), HttpStatusCode.Continue));
+ Writer.WriteToken(HttpHelpers.GetResponseString(ContextInfo.CurrentVersion, HttpStatusCode.Continue));
+
//Trailing crlf
- Writer.WriteLine();
+ Writer.WriteTermination();
+
+ //Get the response data header block
+ Memory<byte> responseBlock = Writer.GetResponseData();
+
//get base stream
- Stream bs = _getStream();
- //Flush writer to stream (will reset the buffer)
- Writer.Flush(ResponseEncoding, bs);
+ Stream bs = ContextInfo.GetTransport();
+
+ //Write the response data to the base stream
+ await bs.WriteAsync(responseBlock);
+
//Flush the base stream
await bs.FlushAsync();
}
+
/// <summary>
/// Sends the status message and all available headers to the client.
/// Headers set after method returns will be sent when output stream is requested or scope exits
@@ -122,53 +130,76 @@ namespace VNLib.Net.Http.Core
public void FlushHeaders()
{
Check();
- //If headers havent been sent yet, start with status code
+
+ //Get a fresh writer to buffer character data
+ ForwardOnlyWriter<char> writer = Writer.GetWriter();
+
+ //If headers havent been sent yet, start with status line
if (!HeadersBegun)
{
//write status code first
- Writer.WriteLine(HttpHelpers.GetResponseString(GetVersion(), _code));
+ writer.Append(HttpHelpers.GetResponseString(ContextInfo.CurrentVersion, _code));
+ writer.Append(HttpHelpers.CRLF);
//Write the date to header buffer
- Writer.Append("Date: ");
- Writer.Append(DateTimeOffset.UtcNow, "R");
- Writer.WriteLine();
+ writer.Append("Date: ");
+ writer.Append(DateTimeOffset.UtcNow, "R");
+ writer.Append(HttpHelpers.CRLF);
+
//Set begun flag
HeadersBegun = true;
}
+
//Write headers
for (int i = 0; i < Headers.Count; i++)
{
- Writer.Append(Headers.Keys[i]); //Write header key
- Writer.Append(": "); //Write separator
- Writer.WriteLine(Headers[i]); //Write the header value
+ writer.Append(Headers.Keys[i]); //Write header key
+ writer.Append(": "); //Write separator
+ writer.Append(Headers[i]); //Write the header value
+ writer.Append(HttpHelpers.CRLF); //Crlf
}
+
//Remove writen headers
Headers.Clear();
+
//Write cookies if any are set
if (Cookies.Count > 0)
{
- //Write cookies if any have been set
+ //Enumerate and write
foreach (HttpCookie cookie in Cookies)
{
- Writer.Append("Set-Cookie: ");
- Writer.Append(in cookie);
- Writer.WriteLine();
+ writer.Append("Set-Cookie: ");
+
+ //Write the cookie to the header buffer
+ cookie.Compile(ref writer);
+
+ writer.Append(HttpHelpers.CRLF);
}
+
//Clear all current cookies
Cookies.Clear();
}
+
+ //Commit headers
+ Writer.CommitChars(ref writer);
}
- private void EndFlushHeaders(Stream transport)
+
+ private ValueTask EndFlushHeadersAsync(Stream transport)
{
//Sent all available headers
FlushHeaders();
+
//Last line to end headers
- Writer.WriteLine();
-
- //Flush writer
- Writer.Flush(ResponseEncoding, transport);
+ Writer.WriteTermination();
+
+ //Get the response data header block
+ Memory<byte> responseBlock = Writer.GetResponseData();
+
//Update sent headers
HeadersSent = true;
+
+ //Write the response data to the base stream
+ return responseBlock.IsEmpty ? ValueTask.CompletedTask : transport.WriteAsync(responseBlock);
}
/// <summary>
@@ -178,14 +209,17 @@ namespace VNLib.Net.Http.Core
/// <returns>A <see cref="Stream"/> configured for writing data to client</returns>
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="InvalidOperationException"></exception>
- public Stream GetStream(long ContentLength)
+ public async ValueTask<Stream> GetStreamAsync(long ContentLength)
{
Check();
+
//Add content length header
Headers[HttpResponseHeader.ContentLength] = ContentLength.ToString();
+
//End sending headers so the user can write to the ouput stream
- Stream transport = _getStream();
- EndFlushHeaders(transport);
+ Stream transport = ContextInfo.GetTransport();
+
+ await EndFlushHeadersAsync(transport);
//Init direct stream
ReusableDirectStream.Prepare(transport);
@@ -200,21 +234,24 @@ namespace VNLib.Net.Http.Core
/// <returns><see cref="Stream"/> supporting chunked encoding</returns>
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="InvalidOperationException"></exception>
- public Stream GetStream()
+ public async ValueTask<Stream> GetStreamAsync()
{
#if DEBUG
- if (GetVersion() != HttpVersion.Http11)
+ if (ContextInfo.CurrentVersion != HttpVersion.Http11)
{
throw new InvalidOperationException("Chunked transfer encoding is not acceptable for this http version");
}
#endif
Check();
+
//Set encoding type to chunked with user-defined compression
Headers[HttpResponseHeader.TransferEncoding] = "chunked";
+
//End sending headers so the user can write to the ouput stream
- Stream transport = _getStream();
- EndFlushHeaders(transport);
-
+ Stream transport = ContextInfo.GetTransport();
+
+ await EndFlushHeadersAsync(transport);
+
//Return the reusable stream
return ReusableChunkedStream;
}
@@ -236,7 +273,7 @@ namespace VNLib.Net.Http.Core
internal async ValueTask CloseAsync()
{
//If headers havent been sent yet, send them and there must be no content
- if (!HeadersBegun)
+ if (!HeadersBegun || !HeadersSent)
{
//RFC 7230, length only set on 200 + but not 204
if ((int)_code >= 200 && (int)_code != 204)
@@ -244,24 +281,13 @@ namespace VNLib.Net.Http.Core
//If headers havent been sent by this stage there is no content, so set length to 0
Headers[HttpResponseHeader.ContentLength] = "0";
}
+
//Flush transport
- Stream transport = _getStream();
- EndFlushHeaders(transport);
- //Flush transport
- await transport.FlushAsync();
- }
- //Headers have been started but not finished yet
- else if (!HeadersSent)
- {
- //RFC 7230, length only set on 200 + but not 204
- if ((int)_code >= 200 && (int)_code != 204)
- {
- //If headers havent been sent by this stage there is no content, so set length to 0
- Headers[HttpResponseHeader.ContentLength] = "0";
- }
- //If headers arent done sending yet, conclude headers
- Stream transport = _getStream();
- EndFlushHeaders(transport);
+ Stream transport = ContextInfo.GetTransport();
+
+ //Finalize headers
+ await EndFlushHeadersAsync(transport);
+
//Flush transport
await transport.FlushAsync();
}
@@ -271,13 +297,11 @@ namespace VNLib.Net.Http.Core
public void OnPrepare()
{
//Propagate all child lifecycle hooks
- Writer.OnPrepare();
ReusableChunkedStream.OnPrepare();
}
public void OnRelease()
{
- Writer.OnRelease();
ReusableChunkedStream.OnRelease();
}
@@ -286,7 +310,6 @@ namespace VNLib.Net.Http.Core
//Default to okay status code
_code = HttpStatusCode.OK;
- Writer.OnNewRequest();
ReusableChunkedStream.OnNewRequest();
}
@@ -299,9 +322,74 @@ namespace VNLib.Net.Http.Core
HeadersBegun = false;
HeadersSent = false;
+ //Reset header writer
+ Writer.Reset();
+
//Call child lifecycle hooks
- Writer.OnComplete();
ReusableChunkedStream.OnComplete();
}
+
+#if DEBUG
+
+ public override string ToString() => Compile();
+
+ public string Compile()
+ {
+ //Alloc char buffer
+ using IMemoryHandle<char> buffer = MemoryUtil.SafeAlloc<char>(16 * 1024);
+
+ //Writer
+ ForwardOnlyWriter<char> writer = new (buffer.Span);
+ Compile(ref writer);
+ return writer.ToString();
+ }
+
+ public void Compile(ref ForwardOnlyWriter<char> writer)
+ {
+ /* READONLY!!! */
+
+ //Status line
+ writer.Append(HttpHelpers.GetResponseString(ContextInfo.CurrentVersion, _code));
+ writer.Append(HttpHelpers.CRLF);
+ writer.Append("Date: ");
+ writer.Append(DateTimeOffset.UtcNow, "R");
+ writer.Append(HttpHelpers.CRLF);
+
+ //Write headers
+ for (int i = 0; i < Headers.Count; i++)
+ {
+ writer.Append(Headers.Keys[i]); //Write header key
+ writer.Append(": "); //Write separator
+ writer.Append(Headers[i]); //Write the header value
+ writer.Append(HttpHelpers.CRLF); //Crlf
+ }
+
+ //Enumerate and write
+ foreach (HttpCookie cookie in Cookies)
+ {
+ writer.Append("Set-Cookie: ");
+
+ //Write the cookie to the header buffer
+ cookie.Compile(ref writer);
+
+ writer.Append(HttpHelpers.CRLF);
+ }
+
+ //Last line to end headers
+ writer.Append(HttpHelpers.CRLF);
+ }
+
+ public ERRNO Compile(in Span<char> buffer)
+ {
+ ForwardOnlyWriter<char> writer = new(buffer);
+ Compile(ref writer);
+ return writer.Written;
+ }
+#else
+
+ public override string ToString() => "HTTP Library was compiled without a DEBUG directive, response logging is not available";
+
+#endif
+
}
} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs b/lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs
deleted file mode 100644
index fb20e68..0000000
--- a/lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Net.Http
-* File: SharedHeaderReaderBuffer.cs
-*
-* SharedHeaderReaderBuffer.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;
-using System.Runtime.InteropServices;
-
-using VNLib.Utils.Memory;
-
-namespace VNLib.Net.Http.Core
-{
- sealed class SharedHeaderReaderBuffer : IHttpLifeCycle
- {
- private UnsafeMemoryHandle<byte>? Handle;
-
- /// <summary>
- /// The size of the binary buffer
- /// </summary>
- public int BinLength { get; }
-
- private readonly int _bufferSize;
-
- internal SharedHeaderReaderBuffer(int length)
- {
- _bufferSize = length + (length * sizeof(char));
-
- //Round to nearest page
- _bufferSize = (int)MemoryUtil.NearestPage(_bufferSize);
-
- //Bin buffer is the specified size
- BinLength = length;
- }
-
- /// <summary>
- /// The binary buffer to store reader information
- /// </summary>
- public Span<byte> BinBuffer => Handle!.Value.Span[..BinLength];
-
- /// <summary>
- /// The char buffer to store read characters in
- /// </summary>
- public Span<char> CharBuffer => MemoryMarshal.Cast<byte, char>(Handle!.Value.Span[BinLength..]);
-
- public void OnPrepare()
- {
- //Alloc the shared buffer
- Handle = CoreBufferHelpers.GetBinBuffer(_bufferSize, true);
- }
-
- public void OnRelease()
- {
- //Free buffer
- Handle?.Dispose();
- Handle = null;
- }
-
- public void OnNewRequest()
- {}
-
- public void OnComplete()
- {
- //Zero buffer
- Handle!.Value.Span.Clear();
- }
- }
-} \ No newline at end of file
diff --git a/lib/Net.Http/src/Helpers/TransportReader.cs b/lib/Net.Http/src/Core/TransportReader.cs
index 722120b..58c23df 100644
--- a/lib/Net.Http/src/Helpers/TransportReader.cs
+++ b/lib/Net.Http/src/Core/TransportReader.cs
@@ -28,6 +28,7 @@ using System.Text;
using VNLib.Utils;
using VNLib.Utils.IO;
+using VNLib.Net.Http.Core.Buffering;
namespace VNLib.Net.Http.Core
{
@@ -39,12 +40,14 @@ namespace VNLib.Net.Http.Core
{
///<inheritdoc/>
public readonly Encoding Encoding { get; }
+
///<inheritdoc/>
public readonly ReadOnlyMemory<byte> LineTermination { get; }
+
///<inheritdoc/>
public readonly Stream BaseStream { get; }
- private readonly SharedHeaderReaderBuffer BinBuffer;
+ private readonly IHttpHeaderParseBuffer Buffer;
private int BufWindowStart;
private int BufWindowEnd;
@@ -56,35 +59,39 @@ 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>
- public TransportReader(Stream transport, SharedHeaderReaderBuffer buffer, Encoding encoding, ReadOnlyMemory<byte> lineTermination)
+ public TransportReader(Stream transport, IHttpHeaderParseBuffer buffer, Encoding encoding, ReadOnlyMemory<byte> lineTermination)
{
BufWindowEnd = 0;
BufWindowStart = 0;
Encoding = encoding;
BaseStream = transport;
LineTermination = lineTermination;
- BinBuffer = buffer;
+ Buffer = buffer;
}
///<inheritdoc/>
public readonly int Available => BufWindowEnd - BufWindowStart;
///<inheritdoc/>
- public readonly Span<byte> BufferedDataWindow => BinBuffer.BinBuffer[BufWindowStart..BufWindowEnd];
+ public readonly Span<byte> BufferedDataWindow => Buffer.GetBinSpan()[BufWindowStart..BufWindowEnd];
///<inheritdoc/>
public void Advance(int count) => BufWindowStart += count;
+
///<inheritdoc/>
public void FillBuffer()
{
//Get a buffer from the end of the current window to the end of the buffer
- Span<byte> bufferWindow = BinBuffer.BinBuffer[BufWindowEnd..];
+ Span<byte> bufferWindow = Buffer.GetBinSpan()[BufWindowEnd..];
+
//Read from stream
int read = BaseStream.Read(bufferWindow);
+
//Update the end of the buffer window to the end of the read data
BufWindowEnd += read;
}
+
///<inheritdoc/>
public ERRNO CompactBufferWindow()
{
@@ -92,18 +99,23 @@ namespace VNLib.Net.Http.Core
if (BufWindowStart > 0)
{
//Get span over engire buffer
- Span<byte> buffer = BinBuffer.BinBuffer;
+ Span<byte> buffer = Buffer.GetBinSpan();
+
//Get data within window
Span<byte> usedData = buffer[BufWindowStart..BufWindowEnd];
+
//Copy remaining to the begining of the buffer
usedData.CopyTo(buffer);
+
//Buffer window start is 0
BufWindowStart = 0;
+
//Buffer window end is now the remaining size
BufWindowEnd = usedData.Length;
}
- //Return the number of bytes of available space
- return BinBuffer.BinLength - BufWindowEnd;
+
+ //Return the number of bytes of available space from the end of the current window
+ return Buffer.BinSize - BufWindowEnd;
}
}
}
diff --git a/lib/Net.Http/src/FileUpload.cs b/lib/Net.Http/src/FileUpload.cs
index 654d682..794c623 100644
--- a/lib/Net.Http/src/FileUpload.cs
+++ b/lib/Net.Http/src/FileUpload.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -24,95 +24,33 @@
using System;
using System.IO;
-using System.Text;
-
-using VNLib.Utils.IO;
-using VNLib.Utils.Memory;
-using VNLib.Utils.Extensions;
-
-using static VNLib.Net.Http.Core.CoreBufferHelpers;
namespace VNLib.Net.Http
{
/// <summary>
/// Represents an file that was received as an entity body, either using Multipart/FormData or as the entity body itself
/// </summary>
- public readonly struct FileUpload
+ /// <param name="ContentType">
+ /// Content type of uploaded file
+ /// </param>
+ /// <param name="FileData">
+ /// The file data captured on upload
+ /// </param>
+ /// <param name="FileName">
+ /// Name of file uploaded
+ /// </param>
+ /// <param name="DisposeStream">
+ /// A value that indicates whether the stream should be disposed when the handle is freed
+ /// </param>
+ public readonly record struct FileUpload(Stream FileData, bool DisposeStream, ContentType ContentType, string? FileName)
{
/// <summary>
- /// Content type of uploaded file
- /// </summary>
- public readonly ContentType ContentType;
- /// <summary>
- /// Name of file uploaded
- /// </summary>
- public readonly string FileName;
- /// <summary>
- /// The file data captured on upload
- /// </summary>
- public readonly Stream FileData;
-
- private readonly bool OwnsHandle;
-
- /// <summary>
- /// Allocates a new binary buffer, encodes, and copies the specified data to a new <see cref="FileUpload"/>
- /// structure of the specified content type
- /// </summary>
- /// <param name="data">The string data to copy</param>
- /// <param name="dataEncoding">The encoding instance to encode the string data from</param>
- /// <param name="filename">The name of the file</param>
- /// <param name="ct">The content type of the file data</param>
- /// <returns>The <see cref="FileUpload"/> container</returns>
- internal static FileUpload FromString(ReadOnlySpan<char> data, Encoding dataEncoding, string filename, ContentType ct)
- {
- //get number of bytes
- int bytes = dataEncoding.GetByteCount(data);
- //get a buffer from the HTTP heap
- MemoryHandle<byte> buffHandle = HttpPrivateHeap.Alloc<byte>(bytes);
- try
- {
- //Convert back to binary
- bytes = dataEncoding.GetBytes(data, buffHandle);
-
- //Create a new memory stream encapsulating the file data
- VnMemoryStream vms = VnMemoryStream.ConsumeHandle(buffHandle, bytes, true);
-
- //Create new upload wrapper
- return new (vms, filename, ct, true);
- }
- catch
- {
- //Make sure the hanle gets disposed if there is an error
- buffHandle.Dispose();
- throw;
- }
- }
-
- /// <summary>
- /// Initialzes a new <see cref="FileUpload"/> structure from the specified data
- /// and file information.
- /// </summary>
- /// <param name="data"></param>
- /// <param name="filename"></param>
- /// <param name="ct"></param>
- /// <param name="ownsHandle"></param>
- public FileUpload(Stream data, string filename, ContentType ct, bool ownsHandle)
- {
- FileName = filename;
- ContentType = ct;
- //Store handle ownership
- OwnsHandle = ownsHandle;
- //Store the stream
- FileData = data;
- }
-
- /// <summary>
- /// Releases any memory the current instance holds if it owns the handles
+ /// Disposes the stream if the handle is owned
/// </summary>
- internal readonly void Free()
+ public readonly void Free()
{
//Dispose the handle if we own it
- if (OwnsHandle)
+ if (DisposeStream)
{
//This should always be synchronous
FileData.Dispose();
diff --git a/lib/Net.Http/src/Helpers/CoreBufferHelpers.cs b/lib/Net.Http/src/Helpers/CoreBufferHelpers.cs
index cdfeac7..e3e5854 100644
--- a/lib/Net.Http/src/Helpers/CoreBufferHelpers.cs
+++ b/lib/Net.Http/src/Helpers/CoreBufferHelpers.cs
@@ -22,23 +22,11 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
-/*
- * This class is meant to provide memory helper methods
- * as a centralized HTTP local memory api.
- *
- * Pools and heaps are privatized to help avoid
- * leaking sensitive HTTP data across other application
- * allocations and help provide memory optimization.
- */
using System;
using System.Buffers;
-using System.Security;
-using System.Threading;
using VNLib.Utils.IO;
-using VNLib.Utils.Memory;
-using VNLib.Utils.Extensions;
namespace VNLib.Net.Http.Core
{
@@ -54,71 +42,6 @@ namespace VNLib.Net.Http.Core
public static ArrayPool<byte> HttpBinBufferPool { get; } = ArrayPool<byte>.Create();
/// <summary>
- /// An <see cref="IUnmangedHeap"/> used for internal HTTP buffers
- /// </summary>
- public static IUnmangedHeap HttpPrivateHeap => _lazyHeap.Value;
-
- private static readonly Lazy<IUnmangedHeap> _lazyHeap = new(MemoryUtil.InitializeNewHeapForProcess, LazyThreadSafetyMode.PublicationOnly);
-
- /// <summary>
- /// Alloctes an unsafe block of memory from the internal heap, or buffer pool
- /// </summary>
- /// <param name="size">The number of elemnts to allocate</param>
- /// <param name="zero">A value indicating of the block should be zeroed before returning</param>
- /// <returns>A handle to the block of memory</returns>
- /// <exception cref="SecurityException"></exception>
- /// <exception cref="OutOfMemoryException"></exception>
- public static UnsafeMemoryHandle<byte> GetBinBuffer(int size, bool zero)
- {
- //Calc buffer size to the nearest page size
- size = (int)MemoryUtil.NearestPage(size);
-
- /*
- * Heap synchronziation may be enabled for our private heap, so we may want
- * to avoid it in favor of performance over private heap segmentation.
- *
- * If synchronization is enabled, use the system heap
- */
-
- if ((HttpPrivateHeap.CreationFlags & HeapCreation.UseSynchronization) > 0)
- {
- return MemoryUtil.UnsafeAlloc(size, zero);
- }
- else
- {
- return HttpPrivateHeap.UnsafeAlloc<byte>(size, zero);
- }
- }
-
- public static IMemoryOwner<byte> GetMemory(int size, bool zero)
- {
- //Calc buffer size to the nearest page size
- size = (int)MemoryUtil.NearestPage(size);
-
- /*
- * Heap synchronziation may be enabled for our private heap, so we may want
- * to avoid it in favor of performance over private heap segmentation.
- *
- * If synchronization is enabled, use the system heap
- */
-
- if ((HttpPrivateHeap.CreationFlags & HeapCreation.UseSynchronization) > 0)
- {
- return MemoryUtil.Shared.DirectAlloc<byte>(size, zero);
- }
- //If the block is larger than an safe array size, avoid LOH pressure
- else if(size > MemoryUtil.MAX_UNSAFE_POOL_SIZE)
- {
- return HttpPrivateHeap.DirectAlloc<byte>(size, zero);
- }
- //Use the array pool to get a memory handle
- else
- {
- return new VnTempBuffer<byte>(HttpBinBufferPool, size, zero);
- }
- }
-
- /// <summary>
/// Gets the remaining data in the reader buffer and prepares a
/// sliding window buffer to read data from
/// </summary>
diff --git a/lib/Net.Http/src/Helpers/HttpHelpers.cs b/lib/Net.Http/src/Helpers/HttpHelpers.cs
index 0937981..d5c471f 100644
--- a/lib/Net.Http/src/Helpers/HttpHelpers.cs
+++ b/lib/Net.Http/src/Helpers/HttpHelpers.cs
@@ -53,6 +53,7 @@ namespace VNLib.Net.Http
/// Extended <see cref="HttpRequestHeader"/> for origin header, DO NOT USE IN <see cref="WebHeaderCollection"/>
/// </summary>
internal const HttpRequestHeader Origin = (HttpRequestHeader)42;
+
/// <summary>
/// Extended <see cref="HttpRequestHeader"/> for Content-Disposition, DO NOT USE IN <see cref="WebHeaderCollection"/>
/// </summary>
@@ -72,7 +73,7 @@ namespace VNLib.Net.Http
* enum value (with some extra support)
*/
- private static readonly IReadOnlyDictionary<string, HttpRequestHeader> RequestHeaderLookup = new Dictionary<string, HttpRequestHeader>()
+ private static readonly IReadOnlyDictionary<string, HttpRequestHeader> RequestHeaderLookup = new Dictionary<string, HttpRequestHeader>(StringComparer.OrdinalIgnoreCase)
{
{"CacheControl", HttpRequestHeader.CacheControl },
{"Connection", HttpRequestHeader.Connection },
@@ -191,6 +192,7 @@ namespace VNLib.Net.Http
* Pre-compute common headers
*/
Dictionary<int, HttpRequestHeader> requestHeaderHashes = new();
+
//Add all HTTP methods
foreach (string headerValue in RequestHeaderLookup.Keys)
{
@@ -199,6 +201,7 @@ namespace VNLib.Net.Http
//Store the http header enum value with the hash-code of the string of said header
requestHeaderHashes[hashCode] = RequestHeaderLookup[headerValue];
}
+
RequestHeaderHashLookup = requestHeaderHashes;
}
}
@@ -211,12 +214,14 @@ namespace VNLib.Net.Http
/// <returns>Http acceptable string representing a content type</returns>
/// <exception cref="KeyNotFoundException"></exception>
public static string GetContentTypeString(ContentType type) => CtToMime[type];
+
/// <summary>
/// Returns the <see cref="ContentType"/> enum value from the MIME string
/// </summary>
/// <param name="type">Content type from request</param>
/// <returns><see cref="ContentType"/> of request, <see cref="ContentType.NonSupported"/> if unknown</returns>
public static ContentType GetContentType(string type) => MimeToCt.GetValueOrDefault(type, ContentType.NonSupported);
+
//Cache control string using mdn reference
//https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
/// <summary>
@@ -260,6 +265,7 @@ namespace VNLib.Net.Http
sb.Append(maxAge);
return sb.ToString();
}
+
/// <summary>
/// Builds a Cache-Control MIME content header from the specified flags
/// </summary>
@@ -268,6 +274,7 @@ namespace VNLib.Net.Http
/// <param name="immutable">Sets the immutable argument</param>
/// <returns>The string representation of the Cache-Control header</returns>
public static string GetCacheString(CacheType type, TimeSpan maxAge, bool immutable = false) => GetCacheString(type, (int)maxAge.TotalSeconds, immutable);
+
/// <summary>
/// Returns an enum value of an httpmethod of an http request method string
/// </summary>
@@ -281,6 +288,7 @@ namespace VNLib.Net.Http
//run the lookup and return not supported if the method was not found
return MethodHashLookup.GetValueOrDefault(hashCode, HttpMethod.None);
}
+
/// <summary>
/// Compares the first 3 bytes of IPV4 ip address or the first 6 bytes of a IPV6. Can be used to determine if the address is local to another address
/// </summary>
@@ -324,6 +332,7 @@ namespace VNLib.Net.Http
}
return false;
}
+
/// <summary>
/// Selects a <see cref="ContentType"/> for a given file extension
/// </summary>
@@ -338,6 +347,7 @@ namespace VNLib.Net.Http
//If the extension is defined, perform a lookup, otherwise return the default
return ExtensionToCt.GetValueOrDefault(extention.ToString(), ContentType.Binary);
}
+
/// <summary>
/// Selects a runtime compiled <see cref="string"/> matching the given <see cref="HttpStatusCode"/> and <see cref="HttpVersion"/>
/// </summary>
@@ -395,17 +405,22 @@ namespace VNLib.Net.Http
{
//First parameter should be the type argument
type = header.SliceBeforeParam(';').Trim().ToString();
+
//Set defaults for name and filename
name = fileName = null;
+
//get the name parameter
ReadOnlySpan<char> nameSpan = header.SliceAfterParam("name=\"");
+
if (!nameSpan.IsEmpty)
{
//Capture the name parameter value and trim it up
name = nameSpan.SliceBeforeParam('"').Trim().ToString();
}
+
//Check for the filename parameter
ReadOnlySpan<char> fileNameSpan = header.SliceAfterParam("filename=\"");
+
if (!fileNameSpan.IsEmpty)
{
//Capture the name parameter value and trim it up
diff --git a/lib/Net.Http/src/HttpBufferConfig.cs b/lib/Net.Http/src/HttpBufferConfig.cs
new file mode 100644
index 0000000..d4dc0f4
--- /dev/null
+++ b/lib/Net.Http/src/HttpBufferConfig.cs
@@ -0,0 +1,82 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpBufferConfig.cs
+*
+* HttpBufferConfig.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/.
+*/
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Holds configuration constants for http protocol buffers
+ /// </summary>
+ public readonly record struct HttpBufferConfig()
+ {
+ /// <summary>
+ /// The maximum buffer size to use when parsing Multi-part/Form-data file uploads
+ /// </summary>
+ /// <remarks>
+ /// This value is used to create the buffer used to read data from the input stream
+ /// into memory for parsing. Form-data uploads must be parsed in memory because
+ /// the data is not delimited by a content length.
+ /// </remarks>
+ public readonly int FormDataBufferSize { get; init; } = 8192;
+
+ /// <summary>
+ /// The buffer size used to read HTTP headers from the transport.
+ /// </summary>
+ /// <remarks>
+ /// Setting this value too low will result in header parsing failures
+ /// and 400 Bad Request responses. Setting it too high can result in
+ /// resource abuse or high memory usage. 8k is usually a good value.
+ /// </remarks>
+ public readonly int RequestHeaderBufferSize { get; init; } = 8192;
+
+ /// <summary>
+ /// The size (in bytes) of the http response header accumulator buffer.
+ /// </summary>
+ /// <remarks>
+ /// Http responses use an internal accumulator to buffer all response headers
+ /// before writing them to the transport in on write operation. If this value
+ /// is too low, the response will fail to write. If it is too high, it
+ /// may cause resource exhaustion or high memory usage.
+ /// </remarks>
+ public readonly int ResponseHeaderBufferSize { get; init; } = 16 * 1024;
+
+ /// <summary>
+ /// The size (in bytes) of the buffer to use to discard unread request entity bodies
+ /// </summary>
+ public readonly int DiscardBufferSize { get; init; } = 64 * 1024;
+
+ /// <summary>
+ /// The size of the buffer to use when writing response data to the transport
+ /// </summary>
+ /// <remarks>
+ /// This value is the size of the buffer used to copy data from the response
+ /// entity stream, to the transport stream.
+ /// </remarks>
+ public readonly int ResponseBufferSize { get; init; } = 32 * 1024;
+
+ /// <summary>
+ /// The size of the buffer used to accumulate chunked response data before writing to the transport
+ /// </summary>
+ public readonly int ChunkedResponseAccumulatorSize { get; init; } = 64 * 1024;
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/HttpConfig.cs b/lib/Net.Http/src/HttpConfig.cs
index 8e73176..6e22fea 100644
--- a/lib/Net.Http/src/HttpConfig.cs
+++ b/lib/Net.Http/src/HttpConfig.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -33,122 +33,92 @@ namespace VNLib.Net.Http
/// <summary>
/// Represents configration variables used to create the instance and manage http connections
/// </summary>
- public readonly struct HttpConfig
+ /// <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 HttpConfig(ILogProvider log)
- {
- ConnectionKeepAlive = TimeSpan.FromSeconds(100);
- ServerLog = log;
- }
-
- /// <summary>
- /// A log provider that all server related log entiries will be written to
- /// </summary>
- public readonly ILogProvider ServerLog { get; }
+
/// <summary>
/// The absolute request entity body size limit in bytes
/// </summary>
public readonly int MaxUploadSize { get; init; } = 5 * 1000 * 1024;
+
/// <summary>
/// The maximum size in bytes allowed for an MIME form-data content type upload
/// </summary>
/// <remarks>Set to 0 to disabled mulit-part/form-data uploads</remarks>
- public readonly int MaxFormDataUploadSize { get; init; } = 40 * 1024;
- /// <summary>
- /// The maximum buffer size to use when parsing Multi-part/Form-data file uploads
- /// </summary>
- /// <remarks>
- /// This value is used to create the buffer used to read data from the input stream
- /// into memory for parsing. Form-data uploads must be parsed in memory because
- /// the data is not delimited by a content length.
- /// </remarks>
- public readonly int FormDataBufferSize { get; init; } = 8192;
+ public readonly int MaxFormDataUploadSize { get; init; } = 40 * 1024;
+
/// <summary>
/// The maximum response entity size in bytes for which the library will allow compresssing response data
/// </summary>
/// <remarks>Set this value to 0 to disable response compression</remarks>
public readonly int CompressionLimit { get; init; } = 1000 * 1024;
+
/// <summary>
/// The minimum size (in bytes) of respones data that will be compressed
/// </summary>
public readonly int CompressionMinimum { get; init; } = 4096;
+
/// <summary>
/// The maximum amount of time to listen for data from a connected, but inactive transport connection
/// before closing them
/// </summary>
- public readonly TimeSpan ConnectionKeepAlive { get; init; }
+ public readonly TimeSpan ConnectionKeepAlive { get; init; } = TimeSpan.FromSeconds(100);
+
/// <summary>
/// The encoding to use when sending and receiving HTTP data
/// </summary>
public readonly Encoding HttpEncoding { get; init; } = Encoding.UTF8;
+
/// <summary>
/// Sets the compression level for response entity streams of all supported types when
/// compression is used.
/// </summary>
public readonly CompressionLevel CompressionLevel { get; init; } = CompressionLevel.Optimal;
+
/// <summary>
/// Sets the default Http version for responses when the client version cannot be parsed from the request
/// </summary>
public readonly HttpVersion DefaultHttpVersion { get; init; } = HttpVersion.Http11;
- /// <summary>
- /// The buffer size used to read HTTP headers from the transport.
- /// </summary>
- /// <remarks>
- /// Setting this value too low will result in header parsing failures
- /// and 400 Bad Request responses. Setting it too high can result in
- /// resource abuse or high memory usage. 8k is usually a good value.
- /// </remarks>
- public readonly int HeaderBufferSize { get; init; } = 8192;
+
/// <summary>
/// The amount of time (in milliseconds) to wait for data on a connection that is in a receive
/// state, aka active receive.
/// </summary>
public readonly int ActiveConnectionRecvTimeout { get; init; } = 5000;
+
/// <summary>
/// The amount of time (in milliseconds) to wait for data to be send to the client before
/// the connection is closed
/// </summary>
public readonly int SendTimeout { get; init; } = 5000;
+
/// <summary>
/// The maximum number of request headers allowed per request
/// </summary>
public readonly int MaxRequestHeaderCount { get; init; } = 100;
+
/// <summary>
/// The maximum number of open transport connections, before 503 errors
/// will be returned and new connections closed.
/// </summary>
/// <remarks>Set to 0 to disable request processing. Causes perminant 503 results</remarks>
- public readonly int MaxOpenConnections { get; init; } = int.MaxValue;
- /// <summary>
- /// The size (in bytes) of the http response header accumulator buffer.
- /// </summary>
- /// <remarks>
- /// Http responses use an internal accumulator to buffer all response headers
- /// before writing them to the transport in on write operation. If this value
- /// is too low, the response will fail to write. If it is too high, it
- /// may cause resource exhaustion or high memory usage.
- /// </remarks>
- public readonly int ResponseHeaderBufferSize { get; init; } = 16 * 1024;
- /// <summary>
- /// The size (in bytes) of the buffer to use to discard unread request entity bodies
- /// </summary>
- public readonly int DiscardBufferSize { get; init; } = 64 * 1024;
- /// <summary>
- /// The size of the buffer to use when writing response data to the transport
- /// </summary>
- /// <remarks>
- /// This value is the size of the buffer used to copy data from the response
- /// entity stream, to the transport stream.
- /// </remarks>
- public readonly int ResponseBufferSize { get; init; } = 32 * 1024;
- /// <summary>
- /// The size of the buffer used to accumulate chunked response data before writing to the transport
- /// </summary>
- public readonly int ChunkedResponseAccumulatorSize { get; init; } = 64 * 1024;
+ public readonly int MaxOpenConnections { get; init; } = int.MaxValue;
+
/// <summary>
/// An <see cref="ILogProvider"/> for writing verbose request logs. Set to <c>null</c>
/// to disable verbose request logging
/// </summary>
public readonly ILogProvider? RequestDebugLog { get; init; } = null;
+
+ /// <summary>
+ /// The buffer configuration for the server
+ /// </summary>
+ public readonly HttpBufferConfig BufferConfig { get; init; } = new();
}
} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/IHttpEvent.cs b/lib/Net.Http/src/IHttpEvent.cs
index ec1dbb5..8cd8f77 100644
--- a/lib/Net.Http/src/Core/IHttpEvent.cs
+++ b/lib/Net.Http/src/IHttpEvent.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -38,6 +38,7 @@ namespace VNLib.Net.Http
/// Current connection information. (Like "$_SERVER" superglobal in PHP)
/// </summary>
IConnectionInfo Server { get; }
+
/// <summary>
/// The <see cref="HttpServer"/> that this connection originated from
/// </summary>
@@ -48,10 +49,12 @@ namespace VNLib.Net.Http
/// </summary>
/// <remarks>Keys are case-insensitive</remarks>
IReadOnlyDictionary<string, string> QueryArgs { get; }
+
/// <summary>
/// If the request body has form data or url encoded arguments they are stored in key value format
/// </summary>
IReadOnlyDictionary<string, string> RequestArgs { get; }
+
/// <summary>
/// Contains all files upladed with current request
/// </summary>
diff --git a/lib/Net.Http/src/IHttpMemoryPool.cs b/lib/Net.Http/src/IHttpMemoryPool.cs
new file mode 100644
index 0000000..f0a548e
--- /dev/null
+++ b/lib/Net.Http/src/IHttpMemoryPool.cs
@@ -0,0 +1,52 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHttpMemoryPool.cs
+*
+* IHttpMemoryPool.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.Buffers;
+
+using VNLib.Utils.Memory;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Represents a single memory pool for the server that allocates buffers per http context.
+ /// on new connections and frees them when the connection is closed.
+ /// </summary>
+ public interface IHttpMemoryPool
+ {
+ /// <summary>
+ /// Allocates a buffer for a new http context connection attachment.
+ /// </summary>
+ /// <param name="bufferSize">The minium size of the buffer required</param>
+ /// <returns>A handle to the allocated buffer</returns>
+ IMemoryOwner<byte> AllocateBufferForContext(int bufferSize);
+
+ /// <summary>
+ /// Allocates arbitrary form data related memory handles that are not tied to a specific http context.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="initialSize">The initial size of the buffer to allocate, which may be expanded as needed</param>
+ /// <returns>The allocated block of memory</returns>
+ MemoryHandle<T> AllocFormDataBuffer<T>(int initialSize) where T: unmanaged;
+ }
+}