aboutsummaryrefslogtreecommitdiff
path: root/lib/Net.Http/src
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-02-14 14:10:27 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2024-02-14 14:10:27 -0500
commit2b1314c1475e7e1831c691cf349cb89c66fa320c (patch)
tree091fc132a2bee2e79a68d8c6d5eb20f1d989a3d2 /lib/Net.Http/src
parentf4e4db7c5320976406feb252ae8f8bdbe9b3e351 (diff)
Squashed commit of the following:
commit ddd8a651b6eb43cfdd49d84056f8b9c34b543992 Author: vnugent <public@vaughnnugent.com> Date: Wed Feb 14 00:15:50 2024 -0500 ci: reduce output noise and update Argon2 build commit cf942959ff2feea03d3eda2ff2a263bdac4d6bc6 Author: vnugent <public@vaughnnugent.com> Date: Mon Feb 12 18:39:18 2024 -0500 chore: update packages and minor fixes commit ab506af9e2de2876b11bb45b3c7e787616c80155 Author: vnugent <public@vaughnnugent.com> Date: Fri Feb 9 21:27:24 2024 -0500 fix: patch and update core runtime service injection commit 7ed5e8b19164c28d3a238bd56878d2161fbea2e4 Author: vnugent <public@vaughnnugent.com> Date: Thu Feb 8 18:26:11 2024 -0500 fork dotnetplugins and make some intial updates/upgrades commit f4cab88d67be5da0953b14bd46fc972d4acc8606 Author: vnugent <public@vaughnnugent.com> Date: Thu Feb 8 12:16:13 2024 -0500 update some heap api functions commit 6035bf7ed8412f1da361cc5feddd860abfaf4fc1 Author: vnugent <public@vaughnnugent.com> Date: Wed Feb 7 22:09:11 2024 -0500 working file-watcher notifications/rework commit 698f8edf694ad9700ee2ce2220e692b496448ff9 Author: vnugent <public@vaughnnugent.com> Date: Wed Feb 7 20:37:28 2024 -0500 remove mem-template and add file-watcher utility commit b17591e0fb363222fcd7d93c2bad4ab1b102385f Author: vnugent <public@vaughnnugent.com> Date: Wed Feb 7 18:28:21 2024 -0500 add small memmove support for known small blocks commit 631be4d4b27fdbcd4b0526e17a128bb0d86911eb Author: vnugent <public@vaughnnugent.com> Date: Wed Feb 7 18:08:02 2024 -0500 setup some readonly ref arguments and convert copy apis to readonly refs commit 2ba8dec68d5cb192e61ad0141d4b460076d3f90a Author: vnugent <public@vaughnnugent.com> Date: Mon Feb 5 18:30:38 2024 -0500 restructure internal memmove strategies commit 25cf02872da980893ad7fb51d4eccc932380582b Author: vnugent <public@vaughnnugent.com> Date: Sun Feb 4 01:29:18 2024 -0500 add http stream interface, profiling -> file read updates commit 757668c44e78864dc69d5713a2cfba6db2ed9a2a Author: vnugent <public@vaughnnugent.com> Date: Fri Feb 2 14:27:04 2024 -0500 streamline data-copy api with proper large block support and net8 feature updates commit f22c1765fd72ab40a10d8ec92a8cb6d9ec1b1a04 Author: vnugent <public@vaughnnugent.com> Date: Mon Jan 29 16:16:23 2024 -0500 check for compression lib updates to close #2 and fix some ci build stuff commit f974bfdef6a795b4a1c04602502ef506ef2587a9 Author: vnugent <public@vaughnnugent.com> Date: Tue Jan 23 17:36:17 2024 -0500 switch allocator libs to lgpl2.1 commit 1fe5e01b329cd27b675000f1a557b784d3c88b56 Author: vnugent <public@vaughnnugent.com> Date: Tue Jan 23 17:05:59 2024 -0500 consolidate allocator packages and close #1 commit 74e1107e522f00b670526193396217f40a6bade7 Author: vnugent <public@vaughnnugent.com> Date: Tue Jan 23 15:43:40 2024 -0500 cache extension api tweaks commit 96ca2b0388a6326b9bb74f3ab2f62eaede6681e0 Author: vnugent <public@vaughnnugent.com> Date: Mon Jan 22 17:54:23 2024 -0500 explicit tcp server args reuse
Diffstat (limited to 'lib/Net.Http/src')
-rw-r--r--lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs16
-rw-r--r--lib/Net.Http/src/Core/HttpEncodedSegment.cs9
-rw-r--r--lib/Net.Http/src/Core/HttpEvent.cs34
-rw-r--r--lib/Net.Http/src/Core/InitDataBuffer.cs24
-rw-r--r--lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs30
-rw-r--r--lib/Net.Http/src/Core/Response/ResponseWriter.cs253
-rw-r--r--lib/Net.Http/src/Core/TransportReader.cs49
-rw-r--r--lib/Net.Http/src/IHttpEvent.cs14
-rw-r--r--lib/Net.Http/src/IHttpStreamResponse.cs44
9 files changed, 286 insertions, 187 deletions
diff --git a/lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs b/lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs
index 0d26017..774ed6a 100644
--- a/lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs
+++ b/lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -63,16 +63,10 @@ namespace VNLib.Net.Http.Core.Buffering
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public virtual ref byte DangerousGetBinRef(int offset)
{
- if (offset >= _handle.Size)
- {
- throw new ArgumentOutOfRangeException(nameof(offset));
- }
-
- //Get ref to pinned memory
- ref byte start = ref _handle.GetRef();
+ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(offset, _handle.Size);
//Add offset to ref
- return ref Unsafe.Add(ref start, offset);
+ return ref Unsafe.Add(ref _handle.GetRef(), offset);
}
///<inheritdoc/>
@@ -85,9 +79,9 @@ namespace VNLib.Net.Http.Core.Buffering
=> (offset + size) < _handle.Size ? _handle.GetSpan(offset, size) : throw new ArgumentOutOfRangeException(nameof(offset));
- private readonly struct HandleState
+ private struct HandleState
{
- private readonly MemoryHandle _handle;
+ private MemoryHandle _handle;
private readonly IntPtr _pointer;
public readonly int Size;
diff --git a/lib/Net.Http/src/Core/HttpEncodedSegment.cs b/lib/Net.Http/src/Core/HttpEncodedSegment.cs
index 1103f83..bcb1f3b 100644
--- a/lib/Net.Http/src/Core/HttpEncodedSegment.cs
+++ b/lib/Net.Http/src/Core/HttpEncodedSegment.cs
@@ -39,7 +39,7 @@ namespace VNLib.Net.Http.Core
/// <param name="Buffer">The buffer containing the segment data</param>
/// <param name="Offset">The offset in the buffer to begin the segment at</param>
/// <param name="Length">The length of the segment</param>
- internal readonly record struct HttpEncodedSegment(byte[] Buffer, uint Offset, uint Length)
+ internal readonly record struct HttpEncodedSegment(byte[] Buffer, uint Offset, ushort Length)
{
/// <summary>
/// Validates the bounds of the array so calls to <see cref="DangerousCopyTo(Span{byte})"/>
@@ -88,8 +88,8 @@ namespace VNLib.Net.Http.Core
ref byte src = ref MemoryMarshal.GetArrayDataReference(Buffer);
//Call memmove with the buffer offset and desired length
- MemoryUtil.Memmove(ref src, Offset, ref output, 0, Length);
- return (int)Length;
+ MemoryUtil.SmallMemmove(ref src, Offset, ref output, 0, Length);
+ return Length;
}
/// <summary>
@@ -99,10 +99,11 @@ namespace VNLib.Net.Http.Core
/// <param name="data">The string data to encode</param>
/// <param name="enc">The encoder used to convert the character data to bytes</param>
/// <returns>The initalized <see cref="HttpEncodedSegment"/> structure</returns>
+ /// <exception cref="OverflowException"></exception>
public static HttpEncodedSegment FromString(string data, Encoding enc)
{
byte[] encoded = enc.GetBytes(data);
- return new HttpEncodedSegment(encoded, 0, (uint)encoded.Length);
+ return new HttpEncodedSegment(encoded, 0, checked((ushort)encoded.Length));
}
}
} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/HttpEvent.cs b/lib/Net.Http/src/Core/HttpEvent.cs
index a86bc20..e6f3b5b 100644
--- a/lib/Net.Http/src/Core/HttpEvent.cs
+++ b/lib/Net.Http/src/Core/HttpEvent.cs
@@ -98,6 +98,7 @@ namespace VNLib.Net.Http
//If stream is empty, ignore it, the server will default to 0 content length and avoid overhead
if (length == 0)
{
+ stream.Dispose();
return;
}
@@ -121,11 +122,12 @@ namespace VNLib.Net.Http
///<inheritdoc/>
void IHttpEvent.CloseResponse(HttpStatusCode code, ContentType type, IMemoryResponseReader entity)
{
- ArgumentNullException.ThrowIfNull(entity, nameof(entity));
+ ArgumentNullException.ThrowIfNull(entity);
//If stream is empty, ignore it, the server will default to 0 content length and avoid overhead
if (entity.Remaining == 0)
{
+ entity.Close();
return;
}
@@ -146,6 +148,36 @@ namespace VNLib.Net.Http
}
}
+ ///<inheritdoc/>
+ void IHttpEvent.CloseResponse(HttpStatusCode code, ContentType type, IHttpStreamResponse stream, long length)
+ {
+ ArgumentNullException.ThrowIfNull(stream);
+ ArgumentOutOfRangeException.ThrowIfNegative(length);
+
+ //If stream is empty, ignore it, the server will default to 0 content length and avoid overhead
+ if (length == 0)
+ {
+ stream.Dispose();
+ return;
+ }
+
+ //Set status code
+ Context.Response.SetStatusCode(code);
+
+ //Finally store the stream input
+ if (!Context.ResponseBody.TrySetResponseBody(stream, length))
+ {
+ throw new InvalidOperationException("A response body has already been set");
+ }
+
+ //User may want to set the content type header themselves
+ if (type != ContentType.NonSupported)
+ {
+ //Set content type header after body
+ Context.Response.Headers.Set(HttpResponseHeader.ContentType, HttpHelpers.GetContentTypeString(type));
+ }
+ }
+
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
internal void Clear()
{
diff --git a/lib/Net.Http/src/Core/InitDataBuffer.cs b/lib/Net.Http/src/Core/InitDataBuffer.cs
index 4d215d9..6a400bb 100644
--- a/lib/Net.Http/src/Core/InitDataBuffer.cs
+++ b/lib/Net.Http/src/Core/InitDataBuffer.cs
@@ -36,20 +36,14 @@ namespace VNLib.Net.Http.Core
/// A structure that buffers data remaining from an initial transport read. Stored
/// data will be read by copying.
/// </summary>
- internal readonly record struct InitDataBuffer
+ internal readonly struct InitDataBuffer(ArrayPool<byte> pool, byte[] buffer, int size)
{
const int POSITION_SEG_SIZE = sizeof(int);
- readonly int _dataSize;
- readonly byte[] _buffer;
- readonly ArrayPool<byte> _pool;
-
- InitDataBuffer(ArrayPool<byte> pool, byte[] buffer, int size)
- {
- _pool = pool;
- _buffer = buffer;
- _dataSize = size;
- }
+ readonly int _dataSize = size;
+ readonly byte[] _buffer = buffer;
+ readonly ArrayPool<byte> _pool = pool;
+
/// <summary>
/// Allocates the correct size buffer for the given data size
@@ -65,7 +59,7 @@ namespace VNLib.Net.Http.Core
return new(pool, buffer, dataSize);
}
- readonly Span<byte> _positionSegment
+ private readonly Span<byte> _positionSegment
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _buffer.AsSpan(0, POSITION_SEG_SIZE);
@@ -126,10 +120,6 @@ namespace VNLib.Net.Http.Core
/// Releases the internal buffer back to its pool
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal readonly void Release()
- {
- //Return buffer back to pool
- _pool.Return(_buffer);
- }
+ internal readonly void Release() => _pool.Return(_buffer);
}
}
diff --git a/lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs b/lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs
index 5e7be39..cacce4c 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) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -38,29 +38,20 @@ namespace VNLib.Net.Http.Core.Response
/// A specialized <see cref="IDataAccumulator{T}"/> for buffering data
/// in Http/1.1 chunks
/// </summary>
- internal readonly struct ChunkDataAccumulator
+ internal readonly struct ChunkDataAccumulator(IChunkAccumulatorBuffer Buffer, IHttpContextInformation Context)
{
/*
* The number of bytes to reserve at the beginning of the buffer
* for the chunk size segment. This is the maximum size of the
*/
public const int ReservedSize = 16;
-
- private readonly IHttpContextInformation Context;
- private readonly IChunkAccumulatorBuffer Buffer;
-
+
/*
* Must always leave enough room for trailing crlf at the end of
* the buffer
*/
private readonly int TotalMaxBufferSize => Buffer.Size - (int)Context.CrlfSegment.Length;
- public ChunkDataAccumulator(IChunkAccumulatorBuffer buffer, IHttpContextInformation context)
- {
- Context = context;
- Buffer = buffer;
- }
-
/// <summary>
/// Complets and returns the memory segment containing the chunk data to send
/// to the client. This also resets the accumulator.
@@ -128,10 +119,8 @@ namespace VNLib.Net.Http.Core.Response
* and use the memory range operator to get the segment from the reserved
* segment, to the actual end of the data segment.
*/
- private readonly Memory<byte> GetCompleteChunk(int reservedOffset, int accumulatedSize)
- {
- return Buffer.GetMemory()[reservedOffset..accumulatedSize];
- }
+ private readonly Memory<byte> GetCompleteChunk(int reservedOffset, int accumulatedSize)
+ => Buffer.GetMemory()[reservedOffset..accumulatedSize];
private static int GetPointerToEndOfUsedBuffer(int accumulatedSize) => accumulatedSize + ReservedSize;
@@ -195,7 +184,14 @@ namespace VNLib.Net.Http.Core.Response
ref byte reservedSegRef = ref buffer.DangerousGetBinRef(reservedOffset);
ref byte chunkSizeBufRef = ref MemoryMarshal.GetReference(chunkSizeBinBuffer);
- MemoryUtil.Memmove(ref chunkSizeBufRef, 0, ref reservedSegRef, 0, (uint)totalChunkBufferBytes);
+ //We know the block is super small
+ MemoryUtil.SmallMemmove(
+ ref chunkSizeBufRef,
+ 0,
+ ref reservedSegRef,
+ 0,
+ (ushort)totalChunkBufferBytes
+ );
return reservedOffset;
}
diff --git a/lib/Net.Http/src/Core/Response/ResponseWriter.cs b/lib/Net.Http/src/Core/Response/ResponseWriter.cs
index de46dce..b5a167c 100644
--- a/lib/Net.Http/src/Core/Response/ResponseWriter.cs
+++ b/lib/Net.Http/src/Core/Response/ResponseWriter.cs
@@ -51,7 +51,7 @@ namespace VNLib.Net.Http.Core.Response
//Buffering is required when a stream is set
///<inheritdoc/>
- public bool BufferRequired => _userState.Stream != null;
+ public bool BufferRequired => _userState.BufferRequired;
///<inheritdoc/>
public long Length => _userState.Legnth;
@@ -62,7 +62,7 @@ namespace VNLib.Net.Http.Core.Response
/// <param name="response">The stream response body to read</param>
/// <param name="length">Explicit length of the stream</param>
/// <returns>True if the response entity could be set, false if it has already been set</returns>
- internal bool TrySetResponseBody(Stream response, long length)
+ internal bool TrySetResponseBody(IHttpStreamResponse response, long length)
{
if (_userState.IsSet)
{
@@ -95,8 +95,28 @@ namespace VNLib.Net.Http.Core.Response
return true;
}
+ /// <summary>
+ /// Attempts to set the response entity
+ /// </summary>
+ /// <param name="rawStream">The raw stream response to set</param>
+ /// <param name="length">Explicit length of the raw stream</param>
+ /// <returns>True if the response entity could be set, false if it has already been set</returns>
+ internal bool TrySetResponseBody(Stream rawStream, long length)
+ {
+ if (_userState.IsSet)
+ {
+ return false;
+ }
+
+ Debug.Assert(rawStream != null, "Raw stream value is null, illegal operation");
+ Debug.Assert(length > -1, "explicit length passed a negative value, illegal operation");
+
+ //Assign user-state
+ _userState = new(rawStream, length);
+ return true;
+ }
+
private ReadOnlyMemory<byte> _readSegment;
- private ForwardOnlyMemoryReader<byte> _streamReader;
#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
@@ -121,107 +141,68 @@ namespace VNLib.Net.Http.Core.Response
//Disposing of memory response can be deferred until the end of the request since its always syncrhonous
}
- else
+ else if(_userState.RawStream != null)
{
- /*
- * When streams are used, callers will submit an explict length value
- * which must be respected. This allows the stream size to differ from
- * the actual content length. This is useful for when the stream is
- * non-seekable, or does not have a known length
- */
-
- long total = 0;
- while (total < Length)
- {
- //get offset wrapper of the total buffer or remaining count
- Memory<byte> offset = buffer[..(int)Math.Min(buffer.Length, Length - total)];
-
- //read
- int read = await _userState.Stream!.ReadAsync(offset);
+ Debug.Assert(!buffer.IsEmpty, "Transfer buffer is required for streaming operations");
- //Guard
- if (read == 0)
- {
- break;
- }
-
- //write only the data that was read (slice)
- await dest.WriteAsync(offset[..read]);
+ await ProcessStreamDataAsync(_userState.GetRawStreamResponse(), dest, buffer);
+ }
+ else
+ {
+ Debug.Assert(!buffer.IsEmpty, "Transfer buffer is required for streaming operations");
- //Update total
- total += read;
- }
+ Debug.Assert(_userState.Stream != null, "Stream value is null, illegal operation");
- //Try to dispose the response stream asyncrhonously since we are done with it
- await _userState!.DisposeStreamAsync();
+ await ProcessStreamDataAsync(_userState.Stream!, dest, buffer);
}
}
///<inheritdoc/>
public async Task WriteEntityAsync<TComp>(TComp comp, IResponseDataWriter writer, Memory<byte> buffer) where TComp : IResponseCompressor
{
+ //try to clamp the buffer size to the compressor block size
+ int maxBufferSize = Math.Min(buffer.Length, comp.BlockSize);
+ if(maxBufferSize > 0)
+ {
+ buffer = buffer[..maxBufferSize];
+ }
+
+ ChunkedResponseWriter<TComp> output = new(writer, comp);
+
//Write a sliding window response
if (_userState.MemResponse != null)
{
while (_userState.MemResponse.Remaining > 0)
{
+ //Get next segment
+ _readSegment = comp.BlockSize > 0 ?
+ _userState.MemResponse.GetRemainingConstrained(comp.BlockSize)
+ : _userState.MemResponse.GetMemory();
+
//Commit output bytes
- if (CompressNextSegment(_userState.MemResponse, comp, writer))
- {
- //Time to flush
- await writer.FlushAsync(false);
- }
+ await output.WriteAsync(_readSegment);
+
+ //Advance by the written amount
+ _userState.MemResponse.Advance(_readSegment.Length);
}
//Disposing of memory response can be deferred until the end of the request since its always syncrhonous
}
- else
+ else if(_userState.RawStream != null)
{
- //Trim buffer to block size if it is set by the compressor
- if (comp.BlockSize > 0)
- {
- buffer = buffer[..comp.BlockSize];
- }
-
- long total = 0;
- while (total < Length) //If length was reached, break
- {
- //get offset wrapper of the total buffer or remaining count
- Memory<byte> offset = buffer[..(int)Math.Min(buffer.Length, Length - total)];
-
- //read
- int read = await _userState.Stream!.ReadAsync(offset, CancellationToken.None);
-
- //Guard
- if (read == 0)
- {
- break;
- }
-
- //Track read bytes and loop until all bytes are read
- _streamReader = new(offset[..read]);
-
- do
- {
- //Compress the buffered data and flush if required
- if (CompressNextSegment(ref _streamReader, comp, writer))
- {
- //Time to flush
- await writer.FlushAsync(false);
- }
-
- } while (_streamReader.WindowSize > 0);
+ Debug.Assert(!buffer.IsEmpty, "Transfer buffer is required for streaming operations");
- //Update total
- total += read;
- }
+ //Configure a raw stream response
+ await ProcessStreamDataAsync(_userState.GetRawStreamResponse(), output, buffer);
+ }
+ else
+ {
+ Debug.Assert(!buffer.IsEmpty, "Transfer buffer is required for streaming operations");
- /*
- * Try to dispose the response stream asyncrhonously since we can safley here
- * otherwise it will be deferred until the end of the request cleanup
- */
- await _userState.DisposeStreamAsync();
+ Debug.Assert(_userState.Stream != null, "Stream value is null, illegal operation");
+ await ProcessStreamDataAsync(_userState.Stream, output, buffer);
}
+
/*
* Once there is no more response data avialable to compress
@@ -231,11 +212,8 @@ namespace VNLib.Net.Http.Core.Response
do
{
- //Get output buffer
- Memory<byte> output = writer.GetMemory();
-
//Flush the compressor output
- int written = comp.Flush(output);
+ int written = comp.Flush(writer.GetMemory());
//No more data to buffer
if (written == 0)
@@ -254,23 +232,41 @@ namespace VNLib.Net.Http.Core.Response
} while (true);
}
- private static bool CompressNextSegment<TComp>(IMemoryResponseReader reader, TComp comp, IResponseDataWriter writer)
- where TComp: IResponseCompressor
+ private async Task ProcessStreamDataAsync<TStream, TWriter>(TStream stream, TWriter dest, Memory<byte> buffer)
+ where TStream: IHttpStreamResponse
+ where TWriter: IDirectResponsWriter
{
- //Read the next segment
- ReadOnlyMemory<byte> readSegment = comp.BlockSize > 0 ? reader.GetRemainingConstrained(comp.BlockSize) : reader.GetMemory();
+ /*
+ * When streams are used, callers will submit an explict length value
+ * which must be respected. This allows the stream size to differ from
+ * the actual content length. This is useful for when the stream is
+ * non-seekable, or does not have a known length
+ */
- //Get output buffer
- Memory<byte> output = writer.GetMemory();
+ long total = 0;
+ while (total < Length)
+ {
+ //get offset wrapper of the total buffer or remaining count
+ Memory<byte> offset = buffer[..(int)Math.Min(buffer.Length, Length - total)];
- //Compress the trimmed block
- CompressionResult res = comp.CompressBlock(readSegment, output);
- ValidateCompressionResult(in res);
+ //read
+ int read = await stream.ReadAsync(offset);
- //Commit input bytes
- reader.Advance(res.BytesRead);
+ //Guard
+ if (read == 0)
+ {
+ break;
+ }
- return writer.Advance(res.BytesWritten) == 0;
+ //write only the data that was read (slice)
+ await dest.WriteAsync(offset[..read]);
+
+ //Update total
+ total += read;
+ }
+
+ //Try to dispose the response stream asyncrhonously since we are done with it
+ await stream.DisposeAsync();
}
private static bool CompressNextSegment<TComp>(ref ForwardOnlyMemoryReader<byte> reader, TComp comp, IResponseDataWriter writer)
@@ -306,22 +302,26 @@ namespace VNLib.Net.Http.Core.Response
_userState = default;
_readSegment = default;
- _streamReader = default;
}
+
private readonly struct ResponsBodyDataState
{
+ public readonly bool IsSet;
+ public readonly bool BufferRequired;
public readonly long Legnth;
- public readonly Stream? Stream;
+ public readonly IHttpStreamResponse? Stream;
public readonly IMemoryResponseReader? MemResponse;
- public readonly bool IsSet;
+ public readonly Stream? RawStream;
- public ResponsBodyDataState(Stream stream, long length)
+ public ResponsBodyDataState(IHttpStreamResponse stream, long length)
{
Legnth = length;
Stream = stream;
MemResponse = null;
+ RawStream = null;
IsSet = true;
+ BufferRequired = true;
}
public ResponsBodyDataState(IMemoryResponseReader reader)
@@ -329,21 +329,62 @@ namespace VNLib.Net.Http.Core.Response
Legnth = reader.Remaining;
MemResponse = reader;
Stream = null;
+ RawStream = null;
IsSet = true;
+ BufferRequired = false;
}
- public readonly ValueTask DisposeStreamAsync()
+ public ResponsBodyDataState(Stream stream, long length)
{
- return Stream?.DisposeAsync() ?? default;
+ Legnth = length;
+ Stream = null;
+ MemResponse = null;
+ RawStream = stream;
+ IsSet = true;
+ BufferRequired = true;
}
+ public readonly HttpstreamResponse GetRawStreamResponse() => new(RawStream!);
+
public readonly void Dispose()
{
- if (IsSet)
+ Stream?.Dispose();
+ MemResponse?.Close();
+ RawStream?.Dispose();
+ }
+ }
+
+ private readonly struct HttpstreamResponse(Stream stream) : IHttpStreamResponse
+ {
+ ///<inheritdoc/>
+ public readonly void Dispose() => stream.Dispose();
+
+ ///<inheritdoc/>
+ public readonly ValueTask DisposeAsync() => stream.DisposeAsync();
+
+ ///<inheritdoc/>
+ public readonly ValueTask<int> ReadAsync(Memory<byte> buffer) => stream!.ReadAsync(buffer, CancellationToken.None);
+ }
+
+ private readonly struct ChunkedResponseWriter<TComp>(IResponseDataWriter writer, TComp comp) : IDirectResponsWriter
+ where TComp : IResponseCompressor
+ {
+
+ public readonly async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer)
+ {
+ //Track read bytes and loop until all bytes are read
+ ForwardOnlyMemoryReader<byte> streamReader = new (buffer);
+
+ do
{
- Stream?.Dispose();
- MemResponse?.Close();
- }
+ //Compress the buffered data and flush if required
+ if (CompressNextSegment(ref streamReader, comp, writer))
+ {
+ //Time to flush
+ await writer.FlushAsync(false);
+ }
+
+ } while (streamReader.WindowSize > 0);
}
}
}
diff --git a/lib/Net.Http/src/Core/TransportReader.cs b/lib/Net.Http/src/Core/TransportReader.cs
index 7349387..0b38121 100644
--- a/lib/Net.Http/src/Core/TransportReader.cs
+++ b/lib/Net.Http/src/Core/TransportReader.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -38,38 +38,27 @@ namespace VNLib.Net.Http.Core
/// <summary>
/// Structure implementation of <see cref="IVnTextReader"/>
/// </summary>
- internal struct TransportReader : IVnTextReader
+ /// <remarks>
+ /// Initializes a new <see cref="TransportReader"/> for reading text lines from the transport stream
+ /// </remarks>
+ /// <param name="transport">The transport stream to read data from</param>
+ /// <param name="buffer">The shared binary buffer</param>
+ /// <param name="encoding">The encoding to use when reading bianry</param>
+ /// <param name="lineTermination">The line delimiter to search for</param>
+ internal struct TransportReader(Stream transport, IHttpHeaderParseBuffer buffer, Encoding encoding, ReadOnlyMemory<byte> lineTermination) : IVnTextReader
{
///<inheritdoc/>
- public readonly Encoding Encoding { get; }
+ public readonly Encoding Encoding => encoding;
///<inheritdoc/>
- public readonly ReadOnlyMemory<byte> LineTermination { get; }
+ public readonly ReadOnlyMemory<byte> LineTermination => lineTermination;
///<inheritdoc/>
- public readonly Stream BaseStream { get; }
+ public readonly Stream BaseStream => transport;
+
+ private readonly uint MaxBufferSize = (uint)buffer.BinSize;
- private readonly IHttpHeaderParseBuffer Buffer;
- private readonly uint MaxBufferSize;
-
- private BufferPosition _position;
-
- /// <summary>
- /// Initializes a new <see cref="TransportReader"/> for reading text lines from the transport stream
- /// </summary>
- /// <param name="transport">The transport stream to read data from</param>
- /// <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, IHttpHeaderParseBuffer buffer, Encoding encoding, ReadOnlyMemory<byte> lineTermination)
- {
- Encoding = encoding;
- BaseStream = transport;
- LineTermination = lineTermination;
- Buffer = buffer;
- MaxBufferSize = (uint)buffer.BinSize;
- _position = default;
- }
+ private BufferPosition _position = default;
/// <summary>
@@ -77,9 +66,9 @@ namespace VNLib.Net.Http.Core
/// </summary>
/// <returns></returns>
private readonly Span<byte> GetDataSegment()
- => Buffer.GetBinSpan((int)_position.WindowStart, (int)_position.GetWindowSize());
+ => buffer.GetBinSpan((int)_position.WindowStart, (int)_position.GetWindowSize());
- private readonly Span<byte> GetRemainingSegment() => Buffer.GetBinSpan((int)_position.WindowEnd);
+ private readonly Span<byte> GetRemainingSegment() => buffer.GetBinSpan((int)_position.WindowEnd);
///<inheritdoc/>
public readonly int Available => (int)_position.GetWindowSize();
@@ -103,7 +92,7 @@ namespace VNLib.Net.Http.Core
public void FillBuffer()
{
//Read from stream into the remaining buffer segment
- int read = BaseStream.Read(GetRemainingSegment());
+ int read = transport.Read(GetRemainingSegment());
Debug.Assert(read > -1, "Read should never be negative");
//Update the end of the buffer window to the end of the read data
@@ -120,7 +109,7 @@ namespace VNLib.Net.Http.Core
if (_position.WindowStart > 0)
{
//Get a ref to the entire buffer segment, then do an in-place move to shift the data to the start of the buffer
- ref byte ptr = ref Buffer.DangerousGetBinRef(0);
+ ref byte ptr = ref buffer.DangerousGetBinRef(0);
MemoryUtil.Memmove(ref ptr, _position.WindowStart, ref ptr, 0, windowSize);
/*
diff --git a/lib/Net.Http/src/IHttpEvent.cs b/lib/Net.Http/src/IHttpEvent.cs
index ce98db6..917f959 100644
--- a/lib/Net.Http/src/IHttpEvent.cs
+++ b/lib/Net.Http/src/IHttpEvent.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -94,6 +94,18 @@ namespace VNLib.Net.Http
void CloseResponse(HttpStatusCode code, ContentType type, IMemoryResponseReader entity);
/// <summary>
+ /// Responds to a client with an <see cref="IHttpStreamResponse"/> containing data to be sent
+ /// to user of a given contentType.
+ /// </summary>
+ /// <param name="code">The http status code</param>
+ /// <param name="type">The entity content type</param>
+ /// <param name="entity">The entity body to stream to the client</param>
+ /// <param name="length">The length in bytes of the stream data</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ void CloseResponse(HttpStatusCode code, ContentType type, IHttpStreamResponse entity, long length);
+
+ /// <summary>
/// Configures the server to change protocols from HTTP to the specified
/// custom protocol handler.
/// </summary>
diff --git a/lib/Net.Http/src/IHttpStreamResponse.cs b/lib/Net.Http/src/IHttpStreamResponse.cs
new file mode 100644
index 0000000..fdf89f7
--- /dev/null
+++ b/lib/Net.Http/src/IHttpStreamResponse.cs
@@ -0,0 +1,44 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHttpStreamResponse.cs
+*
+* IHttpStreamResponse.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.Threading.Tasks;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Represents a stream of data to be sent to a client as an HTTP response.
+ /// </summary>
+ public interface IHttpStreamResponse : IDisposable, IAsyncDisposable
+ {
+ /// <summary>
+ /// Reads data from the stream into the buffer asynchronously
+ /// and returns the number of bytes read, or 0 if the end of the stream
+ /// has been reached.
+ /// </summary>
+ /// <param name="buffer">The output buffer to write data into</param>
+ /// <returns>The number of bytes read into the output buffer</returns>
+ ValueTask<int> ReadAsync(Memory<byte> buffer);
+ }
+} \ No newline at end of file