aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-07-04 16:04:03 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2024-07-04 16:04:03 -0400
commit981ba286e4793de95bf65e6588313411344c4d53 (patch)
treea207743df2475e8579f3f25caca0a8075b42bf89
parent6b8c67888731f7dd210acdb2b1160cdbdbe30d47 (diff)
refactor: Refactor extensions with perf updates
-rw-r--r--lib/Net.Compression/vnlib_compress/CMakeLists.txt2
-rw-r--r--lib/Net.Http/src/AlternateProtocolBase.cs11
-rw-r--r--lib/Net.Http/src/Core/InitDataBuffer.cs10
-rw-r--r--lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs7
-rw-r--r--lib/Net.Transport.SimpleTCP/src/ITcpListner.cs6
-rw-r--r--lib/Net.Transport.SimpleTCP/src/TcpListenerNode.cs6
-rw-r--r--lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs235
-rw-r--r--lib/Plugins.Essentials/src/Extensions/InternalSerializerExtensions.cs62
-rw-r--r--lib/Plugins.Essentials/src/Extensions/JsonResponse.cs117
-rw-r--r--lib/Plugins.Essentials/src/FilePathCache.cs4
-rw-r--r--lib/Plugins.Essentials/src/HttpEntity.cs23
-rw-r--r--lib/Plugins.Essentials/src/Sessions/ISessionExtensions.cs18
-rw-r--r--lib/Plugins.Essentials/src/WebSocketSession.cs22
-rw-r--r--lib/Utils/src/IO/VnMemoryStream.cs14
14 files changed, 258 insertions, 279 deletions
diff --git a/lib/Net.Compression/vnlib_compress/CMakeLists.txt b/lib/Net.Compression/vnlib_compress/CMakeLists.txt
index 0004c60..cbaca97 100644
--- a/lib/Net.Compression/vnlib_compress/CMakeLists.txt
+++ b/lib/Net.Compression/vnlib_compress/CMakeLists.txt
@@ -39,7 +39,7 @@ if(ENABLE_BROTLI)
FetchContent_Declare(
lib_brotli
GIT_REPOSITORY https://github.com/google/brotli.git
- GIT_TAG 04388304a6f8181cc1f022cc9e95dbb3bfe829a3
+ GIT_TAG a528bce9f65be7515a47cec2cbdcd8023822b99b
GIT_PROGRESS TRUE
)
diff --git a/lib/Net.Http/src/AlternateProtocolBase.cs b/lib/Net.Http/src/AlternateProtocolBase.cs
index e7b9a61..ab9f906 100644
--- a/lib/Net.Http/src/AlternateProtocolBase.cs
+++ b/lib/Net.Http/src/AlternateProtocolBase.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -27,8 +27,6 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
-using VNLib.Net.Http.Core;
-
namespace VNLib.Net.Http
{
/// <summary>
@@ -58,8 +56,9 @@ namespace VNLib.Net.Http
try
{
//Call child initialize method
- await RunAsync(transport);
- CancelSource.Cancel();
+ await RunAsync(transport).ConfigureAwait(false);
+
+ await CancelSource.CancelAsync();
}
finally
{
@@ -73,7 +72,7 @@ namespace VNLib.Net.Http
/// <summary>
/// Is the current socket connected using transport security
/// </summary>
- public virtual bool IsSecure { get; init; }
+ public required virtual bool IsSecure { get; init; }
/// <summary>
/// Determines if the instance is pending cancelation
diff --git a/lib/Net.Http/src/Core/InitDataBuffer.cs b/lib/Net.Http/src/Core/InitDataBuffer.cs
index 6d559cd..d66b34a 100644
--- a/lib/Net.Http/src/Core/InitDataBuffer.cs
+++ b/lib/Net.Http/src/Core/InitDataBuffer.cs
@@ -128,11 +128,11 @@ namespace VNLib.Net.Http.Core
int bytesToRead = Math.Min(Remaining, buffer.Length);
MemoryUtil.Memmove(
- ref MemoryMarshal.GetArrayDataReference(_buffer),
- (nuint)GetDataPosition(),
- ref MemoryMarshal.GetReference(buffer),
- 0,
- (nuint)bytesToRead
+ src: in MemoryMarshal.GetArrayDataReference(_buffer),
+ srcOffset: (nuint)GetDataPosition(),
+ dst: ref MemoryMarshal.GetReference(buffer),
+ dstOffset: 0,
+ elementCount: (nuint)bytesToRead
);
//Update position pointer
diff --git a/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs b/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
index 8e4e0e2..dcbd4af 100644
--- a/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
+++ b/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
@@ -570,7 +570,12 @@ namespace VNLib.Net.Http.Core
/// <param name="reader">The <see cref="VnStreamReader"/> to read lines from the transport</param>
/// <returns>0 if the request line was successfully parsed, a status code if the request could not be processed</returns>
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
- public static HttpStatusCode Http1PrepareEntityBody(this HttpRequest Request, ref Http1ParseState parseState, ref TransportReader reader, ref readonly HttpConfig Config)
+ public static HttpStatusCode Http1PrepareEntityBody(
+ this HttpRequest Request,
+ ref Http1ParseState parseState,
+ ref TransportReader reader,
+ ref readonly HttpConfig Config
+ )
{
/*
* Evil mutable struct, get a local mutable reference to the request's
diff --git a/lib/Net.Transport.SimpleTCP/src/ITcpListner.cs b/lib/Net.Transport.SimpleTCP/src/ITcpListner.cs
index 0abf73c..10bfde0 100644
--- a/lib/Net.Transport.SimpleTCP/src/ITcpListner.cs
+++ b/lib/Net.Transport.SimpleTCP/src/ITcpListner.cs
@@ -3,10 +3,10 @@
*
* Library: VNLib
* Package: VNLib.Net.Transport.SimpleTCP
-* File: TcpServer.cs
+* File: ITcpListner.cs
*
-* TcpServer.cs is part of VNLib.Net.Transport.SimpleTCP which is part of the larger
-* VNLib collection of libraries and utilities.
+* ITcpListner.cs is part of VNLib.Net.Transport.SimpleTCP which is part of
+* the larger VNLib collection of libraries and utilities.
*
* VNLib.Net.Transport.SimpleTCP is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
diff --git a/lib/Net.Transport.SimpleTCP/src/TcpListenerNode.cs b/lib/Net.Transport.SimpleTCP/src/TcpListenerNode.cs
index bb0a39b..0badece 100644
--- a/lib/Net.Transport.SimpleTCP/src/TcpListenerNode.cs
+++ b/lib/Net.Transport.SimpleTCP/src/TcpListenerNode.cs
@@ -3,10 +3,10 @@
*
* Library: VNLib
* Package: VNLib.Net.Transport.SimpleTCP
-* File: TcpServer.cs
+* File: TcpListenerNode.cs
*
-* TcpServer.cs is part of VNLib.Net.Transport.SimpleTCP which is part of the larger
-* VNLib collection of libraries and utilities.
+* TcpListenerNode.cs is part of VNLib.Net.Transport.SimpleTCP which is part
+* of the larger VNLib collection of libraries and utilities.
*
* VNLib.Net.Transport.SimpleTCP is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
diff --git a/lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs b/lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs
index 7c0cf94..db30f0f 100644
--- a/lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs
+++ b/lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs
@@ -34,7 +34,7 @@ using System.Runtime.CompilerServices;
using VNLib.Net.Http;
using VNLib.Hashing;
using VNLib.Utils;
-using VNLib.Utils.Memory.Caching;
+using VNLib.Utils.IO;
using static VNLib.Plugins.Essentials.Statics;
namespace VNLib.Plugins.Essentials.Extensions
@@ -44,20 +44,13 @@ namespace VNLib.Plugins.Essentials.Extensions
/// Provides extension methods for manipulating <see cref="HttpEvent"/>s
/// </summary>
public static class EssentialHttpEventExtensions
- {
+ {
+ const int JsonInitBufferSize = 4096;
/*
* Pooled/tlocal serializers
*/
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
@@ -73,7 +66,8 @@ namespace VNLib.Plugins.Essentials.Extensions
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="ContentTypeUnacceptableException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void CloseResponseJson<T>(this IHttpEvent ev, HttpStatusCode code, T response) => CloseResponseJson(ev, code, response, SR_OPTIONS);
+ public static void CloseResponseJson<T>(this IHttpEvent ev, HttpStatusCode code, T response)
+ => CloseResponseJson(ev, code, response, SR_OPTIONS);
/// <summary>
/// Attempts to serialize the JSON object to binary and configure the response for a JSON message body
@@ -90,19 +84,30 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CloseResponseJson<T>(this IHttpEvent ev, HttpStatusCode code, T response, JsonSerializerOptions? options)
{
- JsonResponse rbuf = ResponsePool.Rent();
+ /*
+ * Taking advantage of a new HttpEntity feature that wraps the memory stream
+ * in a reader class that avoids a user-space copy. While the stream and wrapper
+ * must be allocated, it is far safer and easier on the long term process
+ * memory than large static pools the application/user cannot control.
+ */
+
+#pragma warning disable CA2000 // Dispose objects before losing scope
+
+ VnMemoryStream vms = new(JsonInitBufferSize, zero: false);
+
+#pragma warning restore CA2000 // Dispose objects before losing scope
+
try
{
//Serialze the object on the thread local serializer
- LocalSerializer.Value!.Serialize(rbuf, response, options);
+ LocalSerializer.Value!.Serialize(vms, response, options);
//Set the response as the buffer,
- ev.CloseResponse(code, ContentType.Json, rbuf);
+ ev.CloseResponse(code, ContentType.Json, vms, vms.Length);
}
catch
{
- //Return back to pool on error
- ResponsePool.Return(rbuf);
+ vms.Dispose();
throw;
}
}
@@ -119,7 +124,8 @@ namespace VNLib.Plugins.Essentials.Extensions
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="ContentTypeUnacceptableException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void CloseResponseJson(this IHttpEvent ev, HttpStatusCode code, object response, Type type) => CloseResponseJson(ev, code, response, type, SR_OPTIONS);
+ public static void CloseResponseJson(this IHttpEvent ev, HttpStatusCode code, object response, Type type)
+ => CloseResponseJson(ev, code, response, type, SR_OPTIONS);
/// <summary>
/// Attempts to serialize the JSON object to binary and configure the response for a JSON message body
@@ -136,19 +142,29 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CloseResponseJson(this IHttpEvent ev, HttpStatusCode code, object response, Type type, JsonSerializerOptions? options)
{
- JsonResponse rbuf = ResponsePool.Rent();
+ /*
+ * Taking advantage of a new HttpEntity feature that wraps the memory stream
+ * in a reader class that avoids a user-space copy. While the stream and wrapper
+ * must be allocated, it is far safer and easier on the long term process
+ * memory than large static pools the application/user cannot control.
+ */
+#pragma warning disable CA2000 // Dispose objects before losing scope
+
+ VnMemoryStream vms = new(JsonInitBufferSize, zero: false);
+
+#pragma warning restore CA2000 // Dispose objects before losing scope
+
try
{
//Serialze the object on the thread local serializer
- LocalSerializer.Value!.Serialize(rbuf, response, type, options);
+ LocalSerializer.Value!.Serialize(vms, response, type, options);
//Set the response as the buffer,
- ev.CloseResponse(code, ContentType.Json, rbuf);
+ ev.CloseResponse(code, ContentType.Json, vms, vms.Length);
}
catch
{
- //Return back to pool on error
- ResponsePool.Return(rbuf);
+ vms.Dispose();
throw;
}
}
@@ -174,19 +190,30 @@ namespace VNLib.Plugins.Essentials.Extensions
return;
}
- JsonResponse rbuf = ResponsePool.Rent();
+ /*
+ * Taking advantage of a new HttpEntity feature that wraps the memory stream
+ * in a reader class that avoids a user-space copy. While the stream and wrapper
+ * must be allocated, it is far safer and easier on the long term process
+ * memory than large static pools the application/user cannot control.
+ */
+
+#pragma warning disable CA2000 // Dispose objects before losing scope
+
+ VnMemoryStream vms = new(JsonInitBufferSize, zero: false);
+
+#pragma warning restore CA2000 // Dispose objects before losing scope
+
try
{
//Serialze the object on the thread local serializer
- LocalSerializer.Value!.Serialize(rbuf, data);
+ LocalSerializer.Value!.Serialize(vms, data);
//Set the response as the buffer,
- ev.CloseResponse(code, ContentType.Json, rbuf);
+ ev.CloseResponse(code, ContentType.Json, vms, vms.Length);
}
catch
{
- //Return back to pool on error
- ResponsePool.Return(rbuf);
+ vms.Dispose();
throw;
}
}
@@ -325,7 +352,8 @@ namespace VNLib.Plugins.Essentials.Extensions
ArgumentNullException.ThrowIfNull(file);
//Get content type from filename
- ContentType ct = HttpHelpers.GetContentTypeFromFile(file.Name);
+ ContentType ct = HttpHelpers.GetContentTypeFromFile(file.Name);
+
//Set the input as a stream
ev.CloseResponse(code, ct, file, file.Length);
}
@@ -373,7 +401,7 @@ namespace VNLib.Plugins.Essentials.Extensions
//Get new simple memory response
ev.CloseResponse(code, type, entity: new SimpleMemoryResponse(data, encoding));
}
-
+
/// <summary>
/// Close a response to a connection by copying the speciifed binary buffer
/// </summary>
@@ -392,13 +420,12 @@ namespace VNLib.Plugins.Essentials.Extensions
if (data.IsEmpty)
{
- ev.CloseResponse(code);
- return;
+ ev.CloseResponse(code);
+ }
+ else
+ {
+ ev.CloseResponse(code, type, entity: new SimpleMemoryResponse(data));
}
-
- //Get new simple memory response
- IMemoryResponseReader reader = new SimpleMemoryResponse(data);
- ev.CloseResponse(code, type, reader);
}
/// <summary>
@@ -418,15 +445,18 @@ namespace VNLib.Plugins.Essentials.Extensions
ArgumentNullException.ThrowIfNull(filePath);
//See if file exists and is within the root's directory
- if (entity.RequestedRoot.FindResourceInRoot(filePath, out string realPath))
+ if (!entity.RequestedRoot.FindResourceInRoot(filePath, out string realPath))
{
- //get file-info
- FileInfo realFile = new(realPath);
- //Close the response with the file stream
- entity.CloseResponse(code, realFile);
- return true;
+ return false;
}
- return false;
+
+ //get file-info
+ FileInfo realFile = new(realPath);
+
+ //Close the response with the file stream
+ entity.CloseResponse(code, realFile);
+
+ return true;
}
/// <summary>
@@ -514,6 +544,7 @@ namespace VNLib.Plugins.Essentials.Extensions
throw new InvalidJsonRequestException(je);
}
}
+
obj = default;
return false;
}
@@ -535,7 +566,9 @@ namespace VNLib.Plugins.Essentials.Extensions
try
{
//Check for key in argument
- return ev.RequestArgs.TryGetNonEmptyValue(key, out string? value) ? JsonDocument.Parse(value, options) : null;
+ return ev.RequestArgs.TryGetNonEmptyValue(key, out string? value)
+ ? JsonDocument.Parse(value, options)
+ : null;
}
catch (JsonException je)
{
@@ -566,11 +599,13 @@ namespace VNLib.Plugins.Essentials.Extensions
}
FileUpload file = ev.Files[uploadIndex];
+
//Make sure the file is a json file
if (file.ContentType != ContentType.Json)
{
return default;
}
+
try
{
//Beware this will buffer the entire file object before it attmepts to de-serialize it
@@ -601,12 +636,15 @@ namespace VNLib.Plugins.Essentials.Extensions
{
return default;
}
+
FileUpload file = ev.Files[uploadIndex];
+
//Make sure the file is a json file
if (file.ContentType != ContentType.Json)
{
return default;
}
+
try
{
return JsonDocument.Parse(file.FileData);
@@ -639,12 +677,15 @@ namespace VNLib.Plugins.Essentials.Extensions
{
return ValueTask.FromResult<T?>(default);
}
+
FileUpload file = ev.Files[uploadIndex];
+
//Make sure the file is a json file
if (file.ContentType != ContentType.Json)
{
return ValueTask.FromResult<T?>(default);
}
+
//avoid copying the ev struct, so return deserialze task
static async ValueTask<T?> Deserialze(Stream data, JsonSerializerOptions? options, CancellationToken token)
{
@@ -658,6 +699,7 @@ namespace VNLib.Plugins.Essentials.Extensions
throw new InvalidJsonRequestException(je);
}
}
+
return Deserialze(file.FileData, options, ev.EventCancellation);
}
@@ -682,12 +724,15 @@ namespace VNLib.Plugins.Essentials.Extensions
{
return DocTaskDefault;
}
+
FileUpload file = ev.Files[uploadIndex];
+
//Make sure the file is a json file
if (file.ContentType != ContentType.Json)
{
return DocTaskDefault;
}
+
static async Task<JsonDocument?> Deserialze(Stream data, CancellationToken token)
{
try
@@ -700,6 +745,7 @@ namespace VNLib.Plugins.Essentials.Extensions
throw new InvalidJsonRequestException(je);
}
}
+
return Deserialze(file.FileData, ev.EventCancellation);
}
@@ -722,8 +768,10 @@ namespace VNLib.Plugins.Essentials.Extensions
{
return Task.FromResult<T?>(default);
}
+
//Get the file
FileUpload file = ev.Files[uploadIndex];
+
return parser(file.FileData);
}
@@ -746,8 +794,10 @@ namespace VNLib.Plugins.Essentials.Extensions
{
return Task.FromResult<T?>(default);
}
+
//Get the file
FileUpload file = ev.Files[uploadIndex];
+
//Parse the file using the specified parser
return parser(file.FileData, file.ContentTypeString());
}
@@ -771,8 +821,10 @@ namespace VNLib.Plugins.Essentials.Extensions
{
return ValueTask.FromResult<T?>(default);
}
+
//Get the file
FileUpload file = ev.Files[uploadIndex];
+
return parser(file.FileData);
}
@@ -880,6 +932,27 @@ namespace VNLib.Plugins.Essentials.Extensions
public static string ContentTypeString(this in FileUpload upload) => HttpHelpers.GetContentTypeString(upload.ContentType);
/// <summary>
+ /// Copies the contents of the uploaded file to the specified stream asynchronously
+ /// </summary>
+ /// <param name="upload"></param>
+ /// <param name="bufferSize">The size of the buffer to use when copying stream data</param>
+ /// <param name="outputStream">The stream to write the file data to</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns></returns>
+ public static Task CopyToAsync(this in FileUpload upload, Stream outputStream, int bufferSize, CancellationToken cancellation = default)
+ => upload.FileData.CopyToAsync(outputStream, bufferSize, cancellation);
+
+ /// <summary>
+ /// Copies the contents of the uploaded file to the specified stream asynchronously
+ /// </summary>
+ /// <param name="upload"></param>
+ /// <param name="outputStream">The stream to write the file data to</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>A task that resolves when the copy operation has completed</returns>
+ public static Task CopyToAsync(this in FileUpload upload, Stream outputStream, CancellationToken cancellation = default)
+ => upload.FileData.CopyToAsync(outputStream, cancellation);
+
+ /// <summary>
/// Sets the <see cref="HttpControlMask.CompressionDisabled"/> flag on the current
/// <see cref="IHttpEvent"/> instance to disable dynamic compression on the response.
/// </summary>
@@ -907,30 +980,31 @@ namespace VNLib.Plugins.Essentials.Extensions
{
//Must define an accept callback
ArgumentNullException.ThrowIfNull(socketOpenedCallback);
-
- if (PrepWebSocket(entity, subProtocol))
+
+ if (!PrepWebSocket(entity, subProtocol))
{
- //Set a default keep alive if none was specified
- if (keepAlive == default)
- {
- keepAlive = TimeSpan.FromSeconds(30);
- }
+ return false;
+ }
- IAlternateProtocol ws = new WebSocketSession<T>(GetNewSocketId(), socketOpenedCallback)
- {
- SubProtocol = subProtocol,
- IsSecure = entity.Server.IsSecure(),
- UserState = userState,
- KeepAlive = keepAlive,
- };
+ //Set a default keep alive if none was specified
+ if (keepAlive == default)
+ {
+ keepAlive = TimeSpan.FromSeconds(30);
+ }
- //Setup a new websocket session with a new session id
- entity.DangerousChangeProtocol(ws);
+ IAlternateProtocol ws = new WebSocketSession<T>(socketOpenedCallback)
+ {
+ SocketID = GetNewSocketId(),
+ SubProtocol = subProtocol,
+ IsSecure = entity.Server.IsSecure(),
+ UserState = userState,
+ KeepAlive = keepAlive,
+ };
- return true;
- }
+ //Setup a new websocket session with a new session id
+ entity.DangerousChangeProtocol(ws);
- return false;
+ return true;
}
/// <summary>
@@ -952,30 +1026,31 @@ namespace VNLib.Plugins.Essentials.Extensions
{
//Must define an accept callback
ArgumentNullException.ThrowIfNull(entity);
- ArgumentNullException.ThrowIfNull(socketOpenedCallback);
+ ArgumentNullException.ThrowIfNull(socketOpenedCallback);
- if(PrepWebSocket(entity, subProtocol))
+ if (!PrepWebSocket(entity, subProtocol))
{
- //Set a default keep alive if none was specified
- if (keepAlive == default)
- {
- keepAlive = TimeSpan.FromSeconds(30);
- }
+ return false;
+ }
- IAlternateProtocol ws = new WebSocketSession(GetNewSocketId(), socketOpenedCallback)
- {
- SubProtocol = subProtocol,
- IsSecure = entity.Server.IsSecure(),
- KeepAlive = keepAlive,
- };
+ //Set a default keep alive if none was specified
+ if (keepAlive == default)
+ {
+ keepAlive = TimeSpan.FromSeconds(30);
+ }
- //Setup a new websocket session with a new session id
- entity.DangerousChangeProtocol(ws);
+ IAlternateProtocol ws = new WebSocketSession(socketOpenedCallback)
+ {
+ SocketID = GetNewSocketId(),
+ SubProtocol = subProtocol,
+ IsSecure = entity.Server.IsSecure(),
+ KeepAlive = keepAlive,
+ };
- return true;
- }
+ //Setup a new websocket session with a new session id
+ entity.DangerousChangeProtocol(ws);
- return false;
+ return true;
}
private static string GetNewSocketId() => Guid.NewGuid().ToString("N");
diff --git a/lib/Plugins.Essentials/src/Extensions/InternalSerializerExtensions.cs b/lib/Plugins.Essentials/src/Extensions/InternalSerializerExtensions.cs
index 817b673..3a77541 100644
--- a/lib/Plugins.Essentials/src/Extensions/InternalSerializerExtensions.cs
+++ b/lib/Plugins.Essentials/src/Extensions/InternalSerializerExtensions.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials
@@ -23,75 +23,71 @@
*/
using System;
-using System.IO;
using System.Text.Json;
+using VNLib.Utils.IO;
+
namespace VNLib.Plugins.Essentials.Extensions
{
internal static class InternalSerializerExtensions
{
- internal static void Serialize<T>(this Utf8JsonWriter writer, IJsonSerializerBuffer buffer, T value, JsonSerializerOptions? options)
+ internal static void Serialize<T>(this Utf8JsonWriter writer, VnMemoryStream buffer, T value, JsonSerializerOptions? options)
{
- //Get stream
- Stream output = buffer.GetSerialzingStream();
try
{
- //Reset writer
- writer.Reset(output);
-
- //Serialize
+ //Reset and init the output stream
+ writer.Reset(buffer);
+
JsonSerializer.Serialize(writer, value, options);
-
- //flush output
+
writer.Flush();
+
+ buffer.Seek(0, System.IO.SeekOrigin.Begin);
}
finally
{
- buffer.SerializationComplete();
+ writer.Reset();
}
}
- internal static void Serialize(this Utf8JsonWriter writer, IJsonSerializerBuffer buffer, object value, Type type, JsonSerializerOptions? options)
+ internal static void Serialize(this Utf8JsonWriter writer, VnMemoryStream buffer, object value, Type type, JsonSerializerOptions? options)
{
- //Get stream
- Stream output = buffer.GetSerialzingStream();
try
{
- //Reset writer
- writer.Reset(output);
-
- //Serialize
- JsonSerializer.Serialize(writer, value, type, options);
-
- //flush output
+ //Reset and init the output stream
+ writer.Reset(buffer);
+
+ JsonSerializer.Serialize(writer, value, options);
+
writer.Flush();
+
+ buffer.Seek(0, System.IO.SeekOrigin.Begin);
}
finally
{
- buffer.SerializationComplete();
+ writer.Reset();
}
}
- internal static void Serialize(this Utf8JsonWriter writer, IJsonSerializerBuffer buffer, JsonDocument document)
- {
- //Get stream
- Stream output = buffer.GetSerialzingStream();
+ internal static void Serialize(this Utf8JsonWriter writer, VnMemoryStream buffer, JsonDocument document)
+ {
try
{
- //Reset writer
- writer.Reset(output);
-
- //Serialize
+ //Reset and init the output stream
+ writer.Reset(buffer);
+
document.WriteTo(writer);
- //flush output
writer.Flush();
+
+ buffer.Seek(0, System.IO.SeekOrigin.Begin);
}
finally
{
- buffer.SerializationComplete();
+ writer.Reset();
+ ;
}
}
}
diff --git a/lib/Plugins.Essentials/src/Extensions/JsonResponse.cs b/lib/Plugins.Essentials/src/Extensions/JsonResponse.cs
deleted file mode 100644
index 2b1a9ef..0000000
--- a/lib/Plugins.Essentials/src/Extensions/JsonResponse.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
-* Copyright (c) 2024 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials
-* File: JsonResponse.cs
-*
-* JsonResponse.cs is part of VNLib.Plugins.Essentials which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials 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.Plugins.Essentials 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.IO;
-using System.Diagnostics;
-
-using VNLib.Net.Http;
-using VNLib.Utils.IO;
-using VNLib.Utils.Memory.Caching;
-
-namespace VNLib.Plugins.Essentials.Extensions
-{
- internal sealed class JsonResponse(IObjectRental<JsonResponse> pool) : IJsonSerializerBuffer, IMemoryResponseReader, IDisposable
- {
- const int InitBufferSize = 4096;
- const int MaxSizeThreshold = 24 * 1024; //24KB
-
- private readonly IObjectRental<JsonResponse> _pool = pool;
-
- //Stream "owns" the handle, so we cannot dispose the stream
- private readonly VnMemoryStream _stream = new(InitBufferSize, false);
-
- private int _read;
- private ReadOnlyMemory<byte> _dataSegToSend;
-
- //Cleanup any dangling resources dangling somehow
- ~JsonResponse() => Dispose();
-
- ///<inheritdoc/>
- public void Dispose()
- {
- _stream.Dispose();
- GC.SuppressFinalize(this);
- }
-
- ///<inheritdoc/>
- public Stream GetSerialzingStream()
- {
- //Reset stream position
- _stream.Seek(0, SeekOrigin.Begin);
- return _stream;
- }
-
- ///<inheritdoc/>
- public void SerializationComplete()
- {
- //Reset data read position
- _read = 0;
-
- //Update remaining pointer
- 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();
- }
-
-
- ///<inheritdoc/>
- public int Remaining { get; private set; }
-
- ///<inheritdoc/>
- void IMemoryResponseReader.Advance(int written)
- {
- //Update position
- _read += written;
- Remaining -= written;
-
- Debug.Assert(Remaining > 0);
- }
-
- ///<inheritdoc/>
- void IMemoryResponseReader.Close()
- {
- //Reset and return to pool
- _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() => _dataSegToSend.Slice(_read, Remaining);
- }
-} \ No newline at end of file
diff --git a/lib/Plugins.Essentials/src/FilePathCache.cs b/lib/Plugins.Essentials/src/FilePathCache.cs
index 6a53f87..d8f414c 100644
--- a/lib/Plugins.Essentials/src/FilePathCache.cs
+++ b/lib/Plugins.Essentials/src/FilePathCache.cs
@@ -97,8 +97,8 @@ namespace VNLib.Plugins.Essentials
private struct CachedPath
{
- public string Path;
- public long LastStored;
+ public required string Path;
+ public required long LastStored;
}
}
diff --git a/lib/Plugins.Essentials/src/HttpEntity.cs b/lib/Plugins.Essentials/src/HttpEntity.cs
index a4788a3..86f9924 100644
--- a/lib/Plugins.Essentials/src/HttpEntity.cs
+++ b/lib/Plugins.Essentials/src/HttpEntity.cs
@@ -216,13 +216,19 @@ namespace VNLib.Plugins.Essentials
* Stream length also should not cause an integer overflow,
* which also mean position is assumed not to overflow
* or cause an overflow during reading
+ *
+ * Finally not all memory streams allow fetching the internal
+ * buffer, so check that it can be aquired.
*/
- if(stream is MemoryStream ms && length < int.MaxValue)
+ if (stream is MemoryStream ms
+ && length < int.MaxValue
+ && ms.TryGetBuffer(out ArraySegment<byte> arrSeg)
+ )
{
Entity.CloseResponse(
code,
type,
- entity: new MemStreamWrapper(ms, (int)length)
+ entity: new MemStreamWrapper(in arrSeg, ms, (int)length)
);
return;
@@ -340,15 +346,16 @@ namespace VNLib.Plugins.Essentials
}
- private sealed class MemStreamWrapper(MemoryStream memStream, int length) : IMemoryResponseReader
+ private sealed class MemStreamWrapper(ref readonly ArraySegment<byte> data, MemoryStream stream, int length) : IMemoryResponseReader
{
+ readonly ArraySegment<byte> _data = data;
readonly int length = length;
/*
* Stream may be offset by the caller, it needs
* to be respected during streaming.
*/
- int read = (int)memStream.Position;
+ int read = (int)stream.Position;
///<inheritdoc/>
public int Remaining
@@ -364,14 +371,10 @@ namespace VNLib.Plugins.Essentials
public void Advance(int written) => read += written;
///<inheritdoc/>
- public void Close() => memStream.Dispose();
+ public void Close() => stream.Dispose();
///<inheritdoc/>
- public ReadOnlyMemory<byte> GetMemory()
- {
- byte[] intBuffer = memStream.GetBuffer();
- return new ReadOnlyMemory<byte>(intBuffer, read, Remaining);
- }
+ public ReadOnlyMemory<byte> GetMemory() => _data.AsMemory(read, Remaining);
}
}
}
diff --git a/lib/Plugins.Essentials/src/Sessions/ISessionExtensions.cs b/lib/Plugins.Essentials/src/Sessions/ISessionExtensions.cs
index 05d6712..bff32e5 100644
--- a/lib/Plugins.Essentials/src/Sessions/ISessionExtensions.cs
+++ b/lib/Plugins.Essentials/src/Sessions/ISessionExtensions.cs
@@ -78,10 +78,26 @@ namespace VNLib.Plugins.Essentials.Sessions
public static void InitNewSession(this ISession session, IConnectionInfo ci)
{
session.IsCrossOrigin(ci.CrossOrigin);
- session.SetOrigin(ci.Origin?.ToString());
session.SetRefer(ci.Referer?.ToString());
session.SetSecurityProtocol(ci.GetSslProtocol());
session.SetUserAgent(ci.UserAgent);
+
+ /*
+ * If no origin is specified, then we can use the authority of
+ * our current virtual host because it cannot be a cross-origin
+ * request.
+ */
+ if(ci.Origin is null)
+ {
+ string scheme = ci.RequestUri.Scheme;
+ string authority = ci.RequestUri.Authority;
+
+ session.SetOrigin($"{scheme}{authority}");
+ }
+ else
+ {
+ session.SetOrigin(ci.Origin.ToString());
+ }
}
}
diff --git a/lib/Plugins.Essentials/src/WebSocketSession.cs b/lib/Plugins.Essentials/src/WebSocketSession.cs
index e39f352..6c77003 100644
--- a/lib/Plugins.Essentials/src/WebSocketSession.cs
+++ b/lib/Plugins.Essentials/src/WebSocketSession.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials
@@ -62,10 +62,10 @@ namespace VNLib.Plugins.Essentials
/// connection context and the underlying transport. This session is managed by the parent
/// <see cref="HttpServer"/> that it was created on.
/// </summary>
- public class WebSocketSession : AlternateProtocolBase
+ public class WebSocketSession(WebSocketAcceptedCallback callback) : AlternateProtocolBase
{
internal WebSocket? WsHandle;
- internal readonly WebSocketAcceptedCallback AcceptedCallback;
+ internal readonly WebSocketAcceptedCallback AcceptedCallback = callback;
/// <summary>
/// A cancellation token that can be monitored to reflect the state
@@ -76,7 +76,7 @@ namespace VNLib.Plugins.Essentials
/// <summary>
/// Id assigned to this instance on creation
/// </summary>
- public string SocketID { get; }
+ public required string SocketID { get; init; }
/// <summary>
/// Negotiated sub-protocol
@@ -87,13 +87,6 @@ namespace VNLib.Plugins.Essentials
/// The websocket keep-alive interval
/// </summary>
internal TimeSpan KeepAlive { get; init; }
-
- internal WebSocketSession(string socketId, WebSocketAcceptedCallback callback)
- {
- SocketID = socketId;
- //Store the callback function
- AcceptedCallback = callback;
- }
/// <summary>
/// Initialzes the created websocket with the specified protocol
@@ -175,7 +168,8 @@ namespace VNLib.Plugins.Essentials
/// <param name="flags">Websocket message flags</param>
/// <returns></returns>
/// <exception cref="OperationCanceledException"></exception>
- public ValueTask SendAsync(ReadOnlyMemory<byte> buffer, WebSocketMessageType type, WebSocketMessageFlags flags) => WsHandle!.SendAsync(buffer, type, flags, CancellationToken.None);
+ public ValueTask SendAsync(ReadOnlyMemory<byte> buffer, WebSocketMessageType type, WebSocketMessageFlags flags)
+ => WsHandle!.SendAsync(buffer, type, flags, CancellationToken.None);
/// <summary>
@@ -219,8 +213,8 @@ namespace VNLib.Plugins.Essentials
#nullable enable
- internal WebSocketSession(string sessionId, WebSocketAcceptedCallback<T> callback)
- : base(sessionId, (ses) => callback((ses as WebSocketSession<T>)!))
+ internal WebSocketSession(WebSocketAcceptedCallback<T> callback)
+ : base((ses) => callback((ses as WebSocketSession<T>)!))
{
UserState = default;
}
diff --git a/lib/Utils/src/IO/VnMemoryStream.cs b/lib/Utils/src/IO/VnMemoryStream.cs
index 4d51a08..3f14061 100644
--- a/lib/Utils/src/IO/VnMemoryStream.cs
+++ b/lib/Utils/src/IO/VnMemoryStream.cs
@@ -84,7 +84,7 @@ namespace VNLib.Utils.IO
ArgumentNullException.ThrowIfNull(handle);
return handle.CanRealloc || readOnly
- ? new VnMemoryStream(handle, length, readOnly, ownsHandle)
+ ? new VnMemoryStream(handle, existingManager: null, length, readOnly, ownsHandle)
: throw new ArgumentException("The supplied memory handle must be resizable on a writable stream", nameof(handle));
}
@@ -179,7 +179,14 @@ namespace VNLib.Utils.IO
/// <param name="length">The length property of the stream</param>
/// <param name="readOnly">Is the stream readonly (should mostly be true!)</param>
/// <param name="ownsHandle">Does the new stream own the memory -> <paramref name="buffer"/></param>
- private VnMemoryStream(IResizeableMemoryHandle<byte> buffer, nint length, bool readOnly, bool ownsHandle)
+ /// <param name="existingManager">A reference to an existing memory manager class</param>
+ private VnMemoryStream(
+ IResizeableMemoryHandle<byte> buffer,
+ MemoryManager<byte>? existingManager,
+ nint length,
+ bool readOnly,
+ bool ownsHandle
+ )
{
Debug.Assert(length >= 0, "Length must be positive");
Debug.Assert(buffer.CanRealloc || readOnly, "The supplied buffer is not resizable on a writable stream");
@@ -188,6 +195,7 @@ namespace VNLib.Utils.IO
_buffer = buffer; //Consume the handle
_length = length; //Store length of the buffer
_isReadonly = readOnly;
+ _memoryWrapper = existingManager;
}
/// <summary>
@@ -205,7 +213,7 @@ namespace VNLib.Utils.IO
//Create a new readonly copy (stream does not own the handle)
return !_isReadonly
? throw new NotSupportedException("This stream is not readonly. Cannot create shallow copy on a mutable stream")
- : new VnMemoryStream(_buffer, _length, true, false);
+ : new VnMemoryStream(_buffer, _memoryWrapper, _length, readOnly: true, ownsHandle: false);
}
/// <summary>