diff options
Diffstat (limited to 'lib/Plugins.Essentials/src')
3 files changed, 134 insertions, 48 deletions
diff --git a/lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs b/lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs index 0ca5b8f..7c0cf94 100644 --- a/lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs +++ b/lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs @@ -44,16 +44,21 @@ namespace VNLib.Plugins.Essentials.Extensions /// Provides extension methods for manipulating <see cref="HttpEvent"/>s /// </summary> public static class EssentialHttpEventExtensions - { - + { /* * Pooled/tlocal serializers */ - private static ThreadLocal<Utf8JsonWriter> LocalSerializer { get; } = new(() => new(Stream.Null)); - private static IObjectRental<JsonResponse> ResponsePool { get; } = ObjectRental.Create(ResponseCtor); + private static readonly ThreadLocal<Utf8JsonWriter> LocalSerializer = new(() => new(Stream.Null)); + private static readonly ObjectRental<JsonResponse> ResponsePool = ObjectRental.Create(ResponseCtor); + private static JsonResponse ResponseCtor() => new(ResponsePool); + /// <summary> + /// Purges any idle cached JSON responses from the static pool. + /// </summary> + public static void PurgeJsonResponseCache() => ResponsePool.CacheClear(); + #region Response Configuring /// <summary> @@ -366,8 +371,7 @@ namespace VNLib.Plugins.Essentials.Extensions ArgumentNullException.ThrowIfNull(encoding, nameof(encoding)); //Get new simple memory response - IMemoryResponseReader reader = new SimpleMemoryResponse(data, encoding); - ev.CloseResponse(code, type, reader); + ev.CloseResponse(code, type, entity: new SimpleMemoryResponse(data, encoding)); } /// <summary> @@ -435,7 +439,7 @@ namespace VNLib.Plugins.Essentials.Extensions /// <exception cref="UriFormatException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Redirect(this IHttpEvent ev, RedirectType type, string location) - => Redirect(ev, type, new Uri(location, UriKind.RelativeOrAbsolute)); + => Redirect(ev, type, location: new Uri(location, UriKind.RelativeOrAbsolute)); /// <summary> /// Redirects a client using the specified <see cref="RedirectType"/> @@ -939,7 +943,12 @@ namespace VNLib.Plugins.Essentials.Extensions /// <returns>True if operation succeeds.</returns> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="InvalidOperationException"></exception> - public static bool AcceptWebSocket(this IHttpEvent entity, WebSocketAcceptedCallback socketOpenedCallback, string? subProtocol = null, TimeSpan keepAlive = default) + public static bool AcceptWebSocket( + this IHttpEvent entity, + WebSocketAcceptedCallback socketOpenedCallback, + string? subProtocol = null, + TimeSpan keepAlive = default + ) { //Must define an accept callback ArgumentNullException.ThrowIfNull(entity); @@ -971,7 +980,7 @@ namespace VNLib.Plugins.Essentials.Extensions private static string GetNewSocketId() => Guid.NewGuid().ToString("N"); - private static bool PrepWebSocket(this IHttpEvent entity, string? subProtocol = null) + private static bool PrepWebSocket(this IHttpEvent entity, string? subProtocol) { ArgumentNullException.ThrowIfNull(entity); @@ -1006,6 +1015,7 @@ namespace VNLib.Plugins.Essentials.Extensions return true; } } + //Set the client up for a bad request response, nod a valid websocket request entity.CloseResponse(HttpStatusCode.BadRequest); return false; diff --git a/lib/Plugins.Essentials/src/Extensions/JsonResponse.cs b/lib/Plugins.Essentials/src/Extensions/JsonResponse.cs index b418b6f..2b1a9ef 100644 --- a/lib/Plugins.Essentials/src/Extensions/JsonResponse.cs +++ b/lib/Plugins.Essentials/src/Extensions/JsonResponse.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials @@ -24,66 +24,60 @@ using System; using System.IO; -using System.Buffers; +using System.Diagnostics; using VNLib.Net.Http; using VNLib.Utils.IO; -using VNLib.Utils.Memory; -using VNLib.Utils.Extensions; using VNLib.Utils.Memory.Caching; namespace VNLib.Plugins.Essentials.Extensions { - internal sealed class JsonResponse : IJsonSerializerBuffer, IMemoryResponseReader + internal sealed class JsonResponse(IObjectRental<JsonResponse> pool) : IJsonSerializerBuffer, IMemoryResponseReader, IDisposable { - private readonly IObjectRental<JsonResponse> _pool; + const int InitBufferSize = 4096; + const int MaxSizeThreshold = 24 * 1024; //24KB + + private readonly IObjectRental<JsonResponse> _pool = pool; - private readonly MemoryHandle<byte> _handle; - private readonly IMemoryOwner<byte> _memoryOwner; //Stream "owns" the handle, so we cannot dispose the stream - private readonly VnMemoryStream _asStream; - - private int _written; + private readonly VnMemoryStream _stream = new(InitBufferSize, false); - internal JsonResponse(IObjectRental<JsonResponse> pool) - { - /* - * I am breaking the memoryhandle rules by referrencing the same - * memory handle in two different wrappers. - */ + private int _read; + private ReadOnlyMemory<byte> _dataSegToSend; - _pool = pool; - - //Alloc buffer - _handle = MemoryUtil.Shared.Alloc<byte>(4096, false); - - //Create stream around handle and not own it - _asStream = VnMemoryStream.FromHandle(_handle, false, 0, false); - - //Get memory owner from handle - _memoryOwner = _handle.ToMemoryManager(false); - } + //Cleanup any dangling resources dangling somehow + ~JsonResponse() => Dispose(); - ~JsonResponse() + ///<inheritdoc/> + public void Dispose() { - _handle.Dispose(); + _stream.Dispose(); + GC.SuppressFinalize(this); } ///<inheritdoc/> public Stream GetSerialzingStream() { //Reset stream position - _asStream.Seek(0, SeekOrigin.Begin); - return _asStream; + _stream.Seek(0, SeekOrigin.Begin); + return _stream; } ///<inheritdoc/> public void SerializationComplete() { - //Reset written position - _written = 0; + //Reset data read position + _read = 0; + //Update remaining pointer - Remaining = Convert.ToInt32(_asStream.Position); + Remaining = Convert.ToInt32(_stream.Position); + + /* + * Store the written segment for streaming now that the + * serialization is complete. This is the entire window of + * the stream, from 0 - length + */ + _dataSegToSend = _stream.AsMemory(); } @@ -94,21 +88,30 @@ namespace VNLib.Plugins.Essentials.Extensions void IMemoryResponseReader.Advance(int written) { //Update position - _written += written; + _read += written; Remaining -= written; + + Debug.Assert(Remaining > 0); } ///<inheritdoc/> void IMemoryResponseReader.Close() { //Reset and return to pool - _written = 0; + _read = 0; Remaining = 0; + + //if the stream size was pretty large, shrink it before returning to the pool + if (_stream.Length > MaxSizeThreshold) + { + _stream.SetLength(InitBufferSize); + } + //Return self back to pool _pool.Return(this); } ///<inheritdoc/> - ReadOnlyMemory<byte> IMemoryResponseReader.GetMemory() => _memoryOwner.Memory.Slice(_written, Remaining); + ReadOnlyMemory<byte> IMemoryResponseReader.GetMemory() => _dataSegToSend.Slice(_read, Remaining); } }
\ No newline at end of file diff --git a/lib/Plugins.Essentials/src/HttpEntity.cs b/lib/Plugins.Essentials/src/HttpEntity.cs index ff728e3..a4788a3 100644 --- a/lib/Plugins.Essentials/src/HttpEntity.cs +++ b/lib/Plugins.Essentials/src/HttpEntity.cs @@ -30,7 +30,9 @@ using System.Diagnostics; using System.Collections.Generic; using System.Runtime.CompilerServices; +using VNLib.Utils.IO; using VNLib.Net.Http; +using VNLib.Plugins.Essentials.Content; using VNLib.Plugins.Essentials.Sessions; using VNLib.Plugins.Essentials.Extensions; @@ -220,7 +222,41 @@ namespace VNLib.Plugins.Essentials Entity.CloseResponse( code, type, - new MemStreamWrapper(ms, (int)length) + entity: new MemStreamWrapper(ms, (int)length) + ); + + return; + } + + /* + * Readonly vn streams can also use a shortcut to avoid http buffer allocation and + * async streaming. This is done by wrapping the stream in a memory response reader + * + * Allocating a memory manager requires that the stream is readonly + */ + if (stream is VnMemoryStream vms && length < int.MaxValue) + { + Entity.CloseResponse( + code, + type, + entity: new VnStreamWrapper(vms, (int)length) + ); + + return; + } + + /* + * Files can have a bit more performance using the RandomAccess library when reading + * sequential segments without buffering. It avoids a user-space copy and async reading + * performance without the file being opened as async. + */ + if(stream is FileStream fs) + { + Entity.CloseResponse( + code, + type, + entity: new DirectFileStream(fs.SafeFileHandle), + length ); return; @@ -270,6 +306,40 @@ namespace VNLib.Plugins.Essentials void IHttpEvent.DangerousChangeProtocol(IAlternateProtocol protocolHandler) => Entity.DangerousChangeProtocol(protocolHandler); + private sealed class VnStreamWrapper(VnMemoryStream memStream, int length) : IMemoryResponseReader + { + //Store memory buffer, causes an internal allocation, so avoid calling mutliple times + readonly ReadOnlyMemory<byte> _memory = memStream.AsMemory(); + + readonly int length = length; + + /* + * Stream may be offset by the caller, it needs + * to be respected during streaming. + */ + int read = (int)memStream.Position; + + ///<inheritdoc/> + public int Remaining + { + get + { + Debug.Assert(length - read >= 0); + return length - read; + } + } + + ///<inheritdoc/> + public void Advance(int written) => read += written; + + ///<inheritdoc/> + public void Close() => memStream.Dispose(); + + ///<inheritdoc/> + public ReadOnlyMemory<byte> GetMemory() => _memory.Slice(read, Remaining); + } + + private sealed class MemStreamWrapper(MemoryStream memStream, int length) : IMemoryResponseReader { readonly int length = length; @@ -280,6 +350,7 @@ namespace VNLib.Plugins.Essentials */ int read = (int)memStream.Position; + ///<inheritdoc/> public int Remaining { get @@ -289,11 +360,13 @@ namespace VNLib.Plugins.Essentials } } + ///<inheritdoc/> public void Advance(int written) => read += written; ///<inheritdoc/> public void Close() => memStream.Dispose(); + ///<inheritdoc/> public ReadOnlyMemory<byte> GetMemory() { byte[] intBuffer = memStream.GetBuffer(); |