diff options
author | vnugent <public@vaughnnugent.com> | 2024-02-14 14:10:27 -0500 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-02-14 14:10:27 -0500 |
commit | 2b1314c1475e7e1831c691cf349cb89c66fa320c (patch) | |
tree | 091fc132a2bee2e79a68d8c6d5eb20f1d989a3d2 /lib/Net.Http | |
parent | f4e4db7c5320976406feb252ae8f8bdbe9b3e351 (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')
-rw-r--r-- | lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs | 16 | ||||
-rw-r--r-- | lib/Net.Http/src/Core/HttpEncodedSegment.cs | 9 | ||||
-rw-r--r-- | lib/Net.Http/src/Core/HttpEvent.cs | 34 | ||||
-rw-r--r-- | lib/Net.Http/src/Core/InitDataBuffer.cs | 24 | ||||
-rw-r--r-- | lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs | 30 | ||||
-rw-r--r-- | lib/Net.Http/src/Core/Response/ResponseWriter.cs | 253 | ||||
-rw-r--r-- | lib/Net.Http/src/Core/TransportReader.cs | 49 | ||||
-rw-r--r-- | lib/Net.Http/src/IHttpEvent.cs | 14 | ||||
-rw-r--r-- | lib/Net.Http/src/IHttpStreamResponse.cs | 44 |
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 |