From 2b1314c1475e7e1831c691cf349cb89c66fa320c Mon Sep 17 00:00:00 2001 From: vnugent Date: Wed, 14 Feb 2024 14:10:27 -0500 Subject: Squashed commit of the following: commit ddd8a651b6eb43cfdd49d84056f8b9c34b543992 Author: vnugent Date: Wed Feb 14 00:15:50 2024 -0500 ci: reduce output noise and update Argon2 build commit cf942959ff2feea03d3eda2ff2a263bdac4d6bc6 Author: vnugent Date: Mon Feb 12 18:39:18 2024 -0500 chore: update packages and minor fixes commit ab506af9e2de2876b11bb45b3c7e787616c80155 Author: vnugent Date: Fri Feb 9 21:27:24 2024 -0500 fix: patch and update core runtime service injection commit 7ed5e8b19164c28d3a238bd56878d2161fbea2e4 Author: vnugent Date: Thu Feb 8 18:26:11 2024 -0500 fork dotnetplugins and make some intial updates/upgrades commit f4cab88d67be5da0953b14bd46fc972d4acc8606 Author: vnugent Date: Thu Feb 8 12:16:13 2024 -0500 update some heap api functions commit 6035bf7ed8412f1da361cc5feddd860abfaf4fc1 Author: vnugent Date: Wed Feb 7 22:09:11 2024 -0500 working file-watcher notifications/rework commit 698f8edf694ad9700ee2ce2220e692b496448ff9 Author: vnugent Date: Wed Feb 7 20:37:28 2024 -0500 remove mem-template and add file-watcher utility commit b17591e0fb363222fcd7d93c2bad4ab1b102385f Author: vnugent Date: Wed Feb 7 18:28:21 2024 -0500 add small memmove support for known small blocks commit 631be4d4b27fdbcd4b0526e17a128bb0d86911eb Author: vnugent 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 Date: Mon Feb 5 18:30:38 2024 -0500 restructure internal memmove strategies commit 25cf02872da980893ad7fb51d4eccc932380582b Author: vnugent Date: Sun Feb 4 01:29:18 2024 -0500 add http stream interface, profiling -> file read updates commit 757668c44e78864dc69d5713a2cfba6db2ed9a2a Author: vnugent 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 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 Date: Tue Jan 23 17:36:17 2024 -0500 switch allocator libs to lgpl2.1 commit 1fe5e01b329cd27b675000f1a557b784d3c88b56 Author: vnugent Date: Tue Jan 23 17:05:59 2024 -0500 consolidate allocator packages and close #1 commit 74e1107e522f00b670526193396217f40a6bade7 Author: vnugent Date: Tue Jan 23 15:43:40 2024 -0500 cache extension api tweaks commit 96ca2b0388a6326b9bb74f3ab2f62eaede6681e0 Author: vnugent Date: Mon Jan 22 17:54:23 2024 -0500 explicit tcp server args reuse --- .../src/Core/Buffering/HttpBufferElement.cs | 16 +- lib/Net.Http/src/Core/HttpEncodedSegment.cs | 9 +- lib/Net.Http/src/Core/HttpEvent.cs | 34 ++- lib/Net.Http/src/Core/InitDataBuffer.cs | 24 +- .../src/Core/Response/ChunkDataAccumulator.cs | 30 ++- lib/Net.Http/src/Core/Response/ResponseWriter.cs | 253 ++++++++++++--------- lib/Net.Http/src/Core/TransportReader.cs | 49 ++-- lib/Net.Http/src/IHttpEvent.cs | 14 +- lib/Net.Http/src/IHttpStreamResponse.cs | 44 ++++ 9 files changed, 286 insertions(+), 187 deletions(-) create mode 100644 lib/Net.Http/src/IHttpStreamResponse.cs (limited to 'lib/Net.Http/src') 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); } /// @@ -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 /// The buffer containing the segment data /// The offset in the buffer to begin the segment at /// The length of the segment - internal readonly record struct HttpEncodedSegment(byte[] Buffer, uint Offset, uint Length) + internal readonly record struct HttpEncodedSegment(byte[] Buffer, uint Offset, ushort Length) { /// /// Validates the bounds of the array so calls to @@ -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; } /// @@ -99,10 +99,11 @@ namespace VNLib.Net.Http.Core /// The string data to encode /// The encoder used to convert the character data to bytes /// The initalized structure + /// 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 /// 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 } } + /// + 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. /// - internal readonly record struct InitDataBuffer + internal readonly struct InitDataBuffer(ArrayPool pool, byte[] buffer, int size) { const int POSITION_SEG_SIZE = sizeof(int); - readonly int _dataSize; - readonly byte[] _buffer; - readonly ArrayPool _pool; - - InitDataBuffer(ArrayPool pool, byte[] buffer, int size) - { - _pool = pool; - _buffer = buffer; - _dataSize = size; - } + readonly int _dataSize = size; + readonly byte[] _buffer = buffer; + readonly ArrayPool _pool = pool; + /// /// 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 _positionSegment + private readonly Span _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 /// [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 for buffering data /// in Http/1.1 chunks /// - 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; - } - /// /// 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 GetCompleteChunk(int reservedOffset, int accumulatedSize) - { - return Buffer.GetMemory()[reservedOffset..accumulatedSize]; - } + private readonly Memory 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 /// - public bool BufferRequired => _userState.Stream != null; + public bool BufferRequired => _userState.BufferRequired; /// public long Length => _userState.Legnth; @@ -62,7 +62,7 @@ namespace VNLib.Net.Http.Core.Response /// The stream response body to read /// Explicit length of the stream /// True if the response entity could be set, false if it has already been set - 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; } + /// + /// Attempts to set the response entity + /// + /// The raw stream response to set + /// Explicit length of the raw stream + /// True if the response entity could be set, false if it has already been set + 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 _readSegment; - private ForwardOnlyMemoryReader _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 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); } } /// public async Task WriteEntityAsync(TComp comp, IResponseDataWriter writer, Memory 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 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 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 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(IMemoryResponseReader reader, TComp comp, IResponseDataWriter writer) - where TComp: IResponseCompressor + private async Task ProcessStreamDataAsync(TStream stream, TWriter dest, Memory buffer) + where TStream: IHttpStreamResponse + where TWriter: IDirectResponsWriter { - //Read the next segment - ReadOnlyMemory 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 output = writer.GetMemory(); + long total = 0; + while (total < Length) + { + //get offset wrapper of the total buffer or remaining count + Memory 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(ref ForwardOnlyMemoryReader 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 + { + /// + public readonly void Dispose() => stream.Dispose(); + + /// + public readonly ValueTask DisposeAsync() => stream.DisposeAsync(); + + /// + public readonly ValueTask ReadAsync(Memory buffer) => stream!.ReadAsync(buffer, CancellationToken.None); + } + + private readonly struct ChunkedResponseWriter(IResponseDataWriter writer, TComp comp) : IDirectResponsWriter + where TComp : IResponseCompressor + { + + public readonly async ValueTask WriteAsync(ReadOnlyMemory buffer) + { + //Track read bytes and loop until all bytes are read + ForwardOnlyMemoryReader 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 /// /// Structure implementation of /// - internal struct TransportReader : IVnTextReader + /// + /// Initializes a new for reading text lines from the transport stream + /// + /// The transport stream to read data from + /// The shared binary buffer + /// The encoding to use when reading bianry + /// The line delimiter to search for + internal struct TransportReader(Stream transport, IHttpHeaderParseBuffer buffer, Encoding encoding, ReadOnlyMemory lineTermination) : IVnTextReader { /// - public readonly Encoding Encoding { get; } + public readonly Encoding Encoding => encoding; /// - public readonly ReadOnlyMemory LineTermination { get; } + public readonly ReadOnlyMemory LineTermination => lineTermination; /// - 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; - - /// - /// Initializes a new for reading text lines from the transport stream - /// - /// The transport stream to read data from - /// The shared binary buffer - /// The encoding to use when reading bianry - /// The line delimiter to search for - public TransportReader(Stream transport, IHttpHeaderParseBuffer buffer, Encoding encoding, ReadOnlyMemory lineTermination) - { - Encoding = encoding; - BaseStream = transport; - LineTermination = lineTermination; - Buffer = buffer; - MaxBufferSize = (uint)buffer.BinSize; - _position = default; - } + private BufferPosition _position = default; /// @@ -77,9 +66,9 @@ namespace VNLib.Net.Http.Core /// /// private readonly Span GetDataSegment() - => Buffer.GetBinSpan((int)_position.WindowStart, (int)_position.GetWindowSize()); + => buffer.GetBinSpan((int)_position.WindowStart, (int)_position.GetWindowSize()); - private readonly Span GetRemainingSegment() => Buffer.GetBinSpan((int)_position.WindowEnd); + private readonly Span GetRemainingSegment() => buffer.GetBinSpan((int)_position.WindowEnd); /// 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 @@ -93,6 +93,18 @@ namespace VNLib.Net.Http /// void CloseResponse(HttpStatusCode code, ContentType type, IMemoryResponseReader entity); + /// + /// Responds to a client with an containing data to be sent + /// to user of a given contentType. + /// + /// The http status code + /// The entity content type + /// The entity body to stream to the client + /// The length in bytes of the stream data + /// + /// + void CloseResponse(HttpStatusCode code, ContentType type, IHttpStreamResponse entity, long length); + /// /// Configures the server to change protocols from HTTP to the specified /// custom protocol handler. 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 +{ + /// + /// Represents a stream of data to be sent to a client as an HTTP response. + /// + public interface IHttpStreamResponse : IDisposable, IAsyncDisposable + { + /// + /// 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. + /// + /// The output buffer to write data into + /// The number of bytes read into the output buffer + ValueTask ReadAsync(Memory buffer); + } +} \ No newline at end of file -- cgit