aboutsummaryrefslogtreecommitdiff
path: root/lib/Plugins.Essentials
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Plugins.Essentials')
-rw-r--r--lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs28
-rw-r--r--lib/Plugins.Essentials/src/Extensions/JsonResponse.cs79
-rw-r--r--lib/Plugins.Essentials/src/HttpEntity.cs75
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();