diff options
author | vnugent <public@vaughnnugent.com> | 2024-07-04 16:04:03 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-07-04 16:04:03 -0400 |
commit | 981ba286e4793de95bf65e6588313411344c4d53 (patch) | |
tree | a207743df2475e8579f3f25caca0a8075b42bf89 /lib | |
parent | 6b8c67888731f7dd210acdb2b1160cdbdbe30d47 (diff) |
refactor: Refactor extensions with perf updates
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Net.Compression/vnlib_compress/CMakeLists.txt | 2 | ||||
-rw-r--r-- | lib/Net.Http/src/AlternateProtocolBase.cs | 11 | ||||
-rw-r--r-- | lib/Net.Http/src/Core/InitDataBuffer.cs | 10 | ||||
-rw-r--r-- | lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs | 7 | ||||
-rw-r--r-- | lib/Net.Transport.SimpleTCP/src/ITcpListner.cs | 6 | ||||
-rw-r--r-- | lib/Net.Transport.SimpleTCP/src/TcpListenerNode.cs | 6 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs | 235 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/Extensions/InternalSerializerExtensions.cs | 62 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/Extensions/JsonResponse.cs | 117 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/FilePathCache.cs | 4 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/HttpEntity.cs | 23 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/Sessions/ISessionExtensions.cs | 18 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/WebSocketSession.cs | 22 | ||||
-rw-r--r-- | lib/Utils/src/IO/VnMemoryStream.cs | 14 |
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> |