diff options
author | vnugent <public@vaughnnugent.com> | 2024-07-28 19:15:04 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-07-28 19:15:04 -0400 |
commit | 7be5d6648e633ba46a270ca5784de6f4a5a4e0a9 (patch) | |
tree | a6b53de82f12e6778ede2b3974073a6c3b51aace /lib/Plugins.Essentials | |
parent | 1b590c2517fef110564943ed8a10edd11fa758b0 (diff) |
Squashed commit of the following:
commit a4dacd2909426bf628c1eee1253cc5c8a01e2691
Author: vnugent <public@vaughnnugent.com>
Date: Sat Jul 27 22:41:04 2024 -0400
package updates
commit f836e09981866f5c9f2ae46990d11b186a7d12bb
Author: vnugent <public@vaughnnugent.com>
Date: Wed Jul 24 19:15:54 2024 -0400
chore: Remove argon2 docs & optional tcp resuse
commit b9b892ab2143b0ab92e4dcf0a8b043c5c6c17271
Author: vnugent <public@vaughnnugent.com>
Date: Sun Jul 21 20:57:01 2024 -0400
fix spelling Enqueue and deprecate mispelled version
commit 21ffa816f18be4b765ad740ed5d93346ec3b1fda
Author: vnugent <public@vaughnnugent.com>
Date: Sat Jul 20 19:44:31 2024 -0400
static arugment list parsing functions
commit 85cd6793818a3edd0a963bb4829a960ee6b0e022
Author: vnugent <public@vaughnnugent.com>
Date: Mon Jul 15 18:58:06 2024 -0400
chore: Just some minor checks and adjustments
commit abfb5761ee381b7e1e5342a5525ceca8c8fd81dd
Author: vnugent <public@vaughnnugent.com>
Date: Thu Jul 4 23:57:37 2024 -0400
analyzer pass
commit 4a96dbb924f2b5bf80293e4054f221efe67151dd
Author: vnugent <public@vaughnnugent.com>
Date: Thu Jul 4 22:45:28 2024 -0400
package updates
commit 38ad7d923fa8d9e463d4aaa8e35f021086a03f2d
Author: vnugent <public@vaughnnugent.com>
Date: Thu Jul 4 16:20:48 2024 -0400
mimalloc merge upstream upgrades
commit 981ba286e4793de95bf65e6588313411344c4d53
Author: vnugent <public@vaughnnugent.com>
Date: Thu Jul 4 16:04:03 2024 -0400
refactor: Refactor extensions with perf updates
commit 6b8c67888731f7dd210acdb2b1160cdbdbe30d47
Author: vnugent <public@vaughnnugent.com>
Date: Fri Jun 28 15:48:22 2024 -0400
refactor: Update service stack to reflect new loading patterns
commit 12391e9a207b60b41a074600fc2373ad3eb1c3ab
Author: vnugent <public@vaughnnugent.com>
Date: Wed Jun 26 21:01:15 2024 -0400
feat(server): Server arch update, Memory struct access
commit 92e182ceaf843f8d859d38faa8b2c0ff53207ff6
Author: vnugent <public@vaughnnugent.com>
Date: Fri Jun 21 16:02:34 2024 -0400
feat: Multi transport listeners
commit ee3620b8168a42c8e571e853c751ad5999a9b907
Author: vnugent <public@vaughnnugent.com>
Date: Tue Jun 18 21:17:28 2024 -0400
feat: Add file path caching support
commit ff0926be56fc6eafdce36411847d73bf4ce9f183
Author: vnugent <public@vaughnnugent.com>
Date: Sun Jun 16 13:08:31 2024 -0400
feat: Allow multiple plugin loading directories
commit 07ddf6738d32127926d07b1366e56d2a2308b53b
Author: vnugent <public@vaughnnugent.com>
Date: Sun Jun 16 01:12:07 2024 -0400
perf: Absolutely yuge perf boosts
commit ff15c05a9c3e632c39f3889820fb7d889342b452
Author: vnugent <public@vaughnnugent.com>
Date: Fri Jun 14 14:16:24 2024 -0400
fix: Improper request buffer property assignment
commit 7d2987f1d4048c30808a85798e32c99747f6cfe3
Author: vnugent <public@vaughnnugent.com>
Date: Thu Jun 13 21:57:34 2024 -0400
perf: Async pre-buffer to avoid sync buffer
commit 75c1d0cbf9a5a7856c544671a45f1b4312ffe7ce
Author: vnugent <public@vaughnnugent.com>
Date: Tue Jun 11 22:11:45 2024 -0400
feat: Add a default site adapater and interceptors
commit a7c739b7db9a17622cee751fe0e8a10e4b84b48b
Author: vnugent <public@vaughnnugent.com>
Date: Sun Jun 9 13:05:12 2024 -0400
chore: Package updated
commit b4b506a4b6c7c1e90b5b0980e4cfe0460e7546a2
Author: vnugent <public@vaughnnugent.com>
Date: Sat Jun 8 21:54:52 2024 -0400
some minor touchups
commit 2160510fcc22a8574b0090fd91ca29072f45ab59
Author: vnugent <public@vaughnnugent.com>
Date: Fri May 31 15:12:20 2024 -0400
refactor: Immutable tcp listeners
commit 51cb4eb93e4f1b4c47d35b105e72af1fe771abcc
Author: vnugent <public@vaughnnugent.com>
Date: Thu May 30 17:31:16 2024 -0400
refactor: minor non-breaking changes to VNEncoding
commit 768ddc1eb949266d693f96c67d734e881bd59374
Merge: 9a835fe 1b590c2
Author: vnugent <public@vaughnnugent.com>
Date: Wed May 22 17:50:57 2024 -0400
Merge branch 'main' into develop
commit 9a835fe12c9586ab8dd44d7c96fef4a2d6017e4b
Author: vnugent <public@vaughnnugent.com>
Date: Fri May 17 18:27:03 2024 -0400
chore: Update mimmaloc v2.1.6, update fPIC & cleanup
commit 3b7004b88acfc7f7baa3a8857a5a2f7cf3dd560e
Author: vnugent <public@vaughnnugent.com>
Date: Fri May 17 16:03:28 2024 -0400
feat: Added ReadFileDataAsync function
commit 9a964795757bf0da4dd7fcab15ad304f4ea3fdf1
Author: vnugent <public@vaughnnugent.com>
Date: Wed May 15 21:57:39 2024 -0400
refactor: Harden some argon2 password hashing
commit 4035c838c1508af0aa7e767a97431a692958ce1c
Author: vnugent <public@vaughnnugent.com>
Date: Sun May 12 16:55:32 2024 -0400
perf: Utils + http perf mods
commit f4f0d4f74250257991c57bfae74c4852c7e1ae46
Author: vnugent <public@vaughnnugent.com>
Date: Thu May 2 15:22:53 2024 -0400
feat: Buff middleware handlers
|
| Added implicit support for middleware post processing of files before the filehandler closes the connection. Also cleaned up some project file stuff
commit f0b7dca107659df1d7d4631fdbd2aae9d716d053
Merge: 8c4a45e 107b058
Author: vnugent <public@vaughnnugent.com>
Date: Sat Apr 20 12:24:05 2024 -0400
Merge branch 'main' into develop
commit 8c4a45e384accf92b1b6d748530e8d46f7de40d6
Author: vnugent <public@vaughnnugent.com>
Date: Sat Apr 20 11:10:30 2024 -0400
refactor: Overhaul C libraries and fix builds
commit 42ff77080d10b0fc9fecbbc46141e8e23a1d066a
Author: vnugent <public@vaughnnugent.com>
Date: Sat Apr 20 00:45:57 2024 -0400
fix!: Middlware array, multiple cookie set, and cookie check
commit 97e82b9d66f387f9e6d21d88ddc7a8ab8693149c
Merge: 4ca5791 e07537a
Author: vnugent <public@vaughnnugent.com>
Date: Tue Apr 2 13:34:22 2024 -0400
Merge branch 'main' into develop
commit 4ca5791ed67b9834bdbd010206b30373e4705e9b
Author: vnugent <public@vaughnnugent.com>
Date: Tue Apr 2 13:32:12 2024 -0400
fix: Missed ! on null pointer check
commit 9b4036377c52200c6488c98180d69a0e63321f97
Author: vnugent <public@vaughnnugent.com>
Date: Tue Apr 2 13:22:29 2024 -0400
fix: Fix _In_ macro for compression public api
commit 53a7b4b5c5b67b4a4e06e1d9098cac4bcd6afd7c
Merge: 448a93b 21130c8
Author: vnugent <public@vaughnnugent.com>
Date: Sun Mar 31 17:01:15 2024 -0400
Merge branch 'main' into develop
commit 448a93bb1d18d032087afe2476ffccb98634a54c
Author: vnugent <public@vaughnnugent.com>
Date: Sun Mar 31 16:56:51 2024 -0400
ci: fix third-party dir cleanup
commit 9afed1427472da1ea13079f98dbe27339e55ee7d
Author: vnugent <public@vaughnnugent.com>
Date: Sun Mar 31 16:43:15 2024 -0400
perf: Deprecate unsafememoryhandle span extensions
commit 3ff90da4f02af47ea6d233fdd4445337ebe36452
Author: vnugent <public@vaughnnugent.com>
Date: Sat Mar 30 21:36:18 2024 -0400
refactor: Updates, advanced tracing, http optimizations
commit 8d6b79b5ae309b36f265ba81529bcef8bfcd7414
Merge: 6c1667b 5585915
Author: vnugent <public@vaughnnugent.com>
Date: Sun Mar 24 21:01:31 2024 -0400
Merge branch 'main' into develop
commit 6c1667be23597513537f8190e2f55d65eb9b7c7a
Author: vnugent <public@vaughnnugent.com>
Date: Fri Mar 22 12:01:53 2024 -0400
refactor: Overhauled native library loading and lazy init
commit ebf688f2f974295beabf7b5def7e6f6f150551d0
Author: vnugent <public@vaughnnugent.com>
Date: Wed Mar 20 22:16:17 2024 -0400
refactor: Update compression header files and macros + Ci build
commit 9c7b564911080ccd5cbbb9851a0757b05e1e9047
Author: vnugent <public@vaughnnugent.com>
Date: Tue Mar 19 21:54:49 2024 -0400
refactor: JWK overhaul & add length getter to FileUpload
commit 6d8c3444e09561e5957491b3cc1ae858e0abdd14
Author: vnugent <public@vaughnnugent.com>
Date: Mon Mar 18 16:13:20 2024 -0400
feat: Add FNV1a software checksum and basic correction tests
commit 00d182088cecefc08ca80b1faee9bed3f215f40b
Author: vnugent <public@vaughnnugent.com>
Date: Fri Mar 15 01:05:27 2024 -0400
chore: #6 Use utils filewatcher instead of built-in
commit d513c10d9895c6693519ef1d459c6a5a76929436
Author: vnugent <public@vaughnnugent.com>
Date: Sun Mar 10 21:58:14 2024 -0400
source tree project location updated
Diffstat (limited to 'lib/Plugins.Essentials')
-rw-r--r-- | lib/Plugins.Essentials/src/EventProcessor.cs | 21 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/EventProcessorConfig.cs | 6 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs | 247 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/Extensions/InternalSerializerExtensions.cs | 62 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/Extensions/JsonResponse.cs | 114 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/FilePathCache.cs | 123 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/HttpEntity.cs | 88 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/Sessions/ISessionExtensions.cs | 18 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/Sessions/SessionBase.cs | 2 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/Sessions/SessionInfo.cs | 36 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/WebSocketSession.cs | 22 |
11 files changed, 456 insertions, 283 deletions
diff --git a/lib/Plugins.Essentials/src/EventProcessor.cs b/lib/Plugins.Essentials/src/EventProcessor.cs index ba7aa3c..908ad07 100644 --- a/lib/Plugins.Essentials/src/EventProcessor.cs +++ b/lib/Plugins.Essentials/src/EventProcessor.cs @@ -159,6 +159,8 @@ namespace VNLib.Plugins.Essentials private readonly MiddlewareController _middleware = new(config); + private readonly FilePathCache _pathCache = FilePathCache.GetCacheStore(config.FilePathCacheMaxAge); + ///<inheritdoc/> public virtual async ValueTask ClientConnectedAsync(IHttpEvent httpEvent) { @@ -620,6 +622,25 @@ namespace VNLib.Plugins.Essentials /// <returns>True if the resource exists and is allowed to be accessed</returns> public bool FindResourceInRoot(string resourcePath, out string path) { + //Try to get the translated file path from cache + if (_pathCache.TryGetMappedPath(resourcePath, out path)) + { + return true; + } + + //Cache miss, force a lookup + if (FindFileResourceInternal(resourcePath, out path)) + { + //Store the path in the cache for next lookup + _pathCache.StorePathMapping(resourcePath, path); + return true; + } + + return false; + } + + private bool FindFileResourceInternal(string resourcePath, out string path) + { //Check after fully qualified path name because above is a special case path = TranslateResourcePath(resourcePath); string extension = Path.GetExtension(path); diff --git a/lib/Plugins.Essentials/src/EventProcessorConfig.cs b/lib/Plugins.Essentials/src/EventProcessorConfig.cs index 6e101eb..831f5dc 100644 --- a/lib/Plugins.Essentials/src/EventProcessorConfig.cs +++ b/lib/Plugins.Essentials/src/EventProcessorConfig.cs @@ -90,5 +90,11 @@ namespace VNLib.Plugins.Essentials /// A <see cref="TimeSpan"/> for how long a connection may remain open before all operations are cancelled /// </summary> public TimeSpan ExecutionTimeout { get; init; } = TimeSpan.Zero; + + /// <summary> + /// Enables or disables the use of the file path cache. If set to zero , the cache will be disabled, + /// otherwise sets the maximum amount of time a file path is to be cached. + /// </summary> + public TimeSpan FilePathCacheMaxAge { get; init; } = TimeSpan.Zero; } }
\ No newline at end of file diff --git a/lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs b/lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs index 0ca5b8f..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,15 +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 ThreadLocal<Utf8JsonWriter> LocalSerializer { get; } = new(() => new(Stream.Null)); - private static IObjectRental<JsonResponse> ResponsePool { get; } = ObjectRental.Create(ResponseCtor); - private static JsonResponse ResponseCtor() => new(ResponsePool); + private static readonly ThreadLocal<Utf8JsonWriter> LocalSerializer = new(() => new(Stream.Null)); #region Response Configuring @@ -68,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 @@ -85,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; } } @@ -114,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 @@ -131,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; } } @@ -169,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; } } @@ -320,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); } @@ -366,10 +399,9 @@ 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> /// Close a response to a connection by copying the speciifed binary buffer /// </summary> @@ -388,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> @@ -414,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> @@ -435,7 +469,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"/> @@ -510,6 +544,7 @@ namespace VNLib.Plugins.Essentials.Extensions throw new InvalidJsonRequestException(je); } } + obj = default; return false; } @@ -531,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) { @@ -562,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 @@ -597,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); @@ -635,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) { @@ -654,6 +699,7 @@ namespace VNLib.Plugins.Essentials.Extensions throw new InvalidJsonRequestException(je); } } + return Deserialze(file.FileData, options, ev.EventCancellation); } @@ -678,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 @@ -696,6 +745,7 @@ namespace VNLib.Plugins.Essentials.Extensions throw new InvalidJsonRequestException(je); } } + return Deserialze(file.FileData, ev.EventCancellation); } @@ -718,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); } @@ -742,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()); } @@ -767,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); } @@ -876,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> @@ -903,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> @@ -939,39 +1017,45 @@ 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); - 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"); - private static bool PrepWebSocket(this IHttpEvent entity, string? subProtocol = null) + private static bool PrepWebSocket(this IHttpEvent entity, string? subProtocol) { ArgumentNullException.ThrowIfNull(entity); @@ -1006,6 +1090,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/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 b418b6f..0000000 --- a/lib/Plugins.Essentials/src/Extensions/JsonResponse.cs +++ /dev/null @@ -1,114 +0,0 @@ -/* -* Copyright (c) 2023 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.Buffers; - -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 - { - private readonly IObjectRental<JsonResponse> _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; - - internal JsonResponse(IObjectRental<JsonResponse> pool) - { - /* - * I am breaking the memoryhandle rules by referrencing the same - * memory handle in two different wrappers. - */ - - _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); - } - - ~JsonResponse() - { - _handle.Dispose(); - } - - ///<inheritdoc/> - public Stream GetSerialzingStream() - { - //Reset stream position - _asStream.Seek(0, SeekOrigin.Begin); - return _asStream; - } - - ///<inheritdoc/> - public void SerializationComplete() - { - //Reset written position - _written = 0; - //Update remaining pointer - Remaining = Convert.ToInt32(_asStream.Position); - } - - - ///<inheritdoc/> - public int Remaining { get; private set; } - - ///<inheritdoc/> - void IMemoryResponseReader.Advance(int written) - { - //Update position - _written += written; - Remaining -= written; - } - - ///<inheritdoc/> - void IMemoryResponseReader.Close() - { - //Reset and return to pool - _written = 0; - Remaining = 0; - //Return self back to pool - _pool.Return(this); - } - - ///<inheritdoc/> - ReadOnlyMemory<byte> IMemoryResponseReader.GetMemory() => _memoryOwner.Memory.Slice(_written, Remaining); - } -}
\ No newline at end of file diff --git a/lib/Plugins.Essentials/src/FilePathCache.cs b/lib/Plugins.Essentials/src/FilePathCache.cs new file mode 100644 index 0000000..d8f414c --- /dev/null +++ b/lib/Plugins.Essentials/src/FilePathCache.cs @@ -0,0 +1,123 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials +* File: FilePathCache.cs +* +* FilePathCache.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.Diagnostics; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; + +namespace VNLib.Plugins.Essentials +{ + + /// <summary> + /// Represents a cache store for translated file paths to avoid + /// path probing and file system syscalls + /// </summary> + internal abstract class FilePathCache + { + + public abstract bool TryGetMappedPath(string filePath, [NotNullWhen(true)] out string? cachedPath); + + /// <summary> + /// Attempts to store a path mapping in the cache store + /// </summary> + /// <param name="requestPath">The requested input path</param> + /// <param name="filePath">The filesystem path this requested path maps to</param> + public abstract void StorePathMapping(string requestPath, string filePath); + + /// <summary> + /// Creates a new cache store with the specified max age. If max age is zero, the + /// cache store will be disabled. + /// </summary> + /// <param name="maxAge">The max time to store the cahced path reecord</param> + /// <returns>The cache store</returns> + public static FilePathCache GetCacheStore(TimeSpan maxAge) + { + return maxAge == TimeSpan.Zero + ? new DisabledCacheStore() + : new DictBackedFilePathCache(maxAge); + } + + /* + * A very basic dictionary cache that stores translated paths + * from a request input path to a filesystem path. + * + * This must be thread safe as it's called in a multithreaded context. + */ + private sealed class DictBackedFilePathCache(TimeSpan maxAge) : FilePathCache + { + private readonly ConcurrentDictionary<string, CachedPath> _pathCache = new(StringComparer.OrdinalIgnoreCase); + + ///<inheritdoc/> + public override bool TryGetMappedPath(string filePath, [NotNullWhen(true)] out string? cachedPath) + { + if (_pathCache.TryGetValue(filePath, out CachedPath cp)) + { + //TODO: Implement a cache eviction policy + cachedPath = cp.Path; + return true; + } + + cachedPath = null; + return false; + } + + ///<inheritdoc/> + public override void StorePathMapping(string requestPath, string filePath) + { + ArgumentException.ThrowIfNullOrWhiteSpace(requestPath); + + //Cache path is an internal assignment. Should never be null + Debug.Assert(filePath is not null); + + //TODO: Implement a cache eviction policy + _pathCache[requestPath] = new CachedPath { Path = filePath, LastStored = DateTime.MinValue.Ticks }; + } + + private struct CachedPath + { + public required string Path; + public required long LastStored; + } + } + + /* + * A cache store that does nothing, it always misses and will + * cause a normal file fetch + */ + private sealed class DisabledCacheStore : FilePathCache + { + ///<inheritdoc/> + public override void StorePathMapping(string requestPath, string filePath) + { } + + ///<inheritdoc/> + public override bool TryGetMappedPath(string filePath, [NotNullWhen(true)] out string? cachedPath) + { + cachedPath = null; + return false; + } + } + } +} diff --git a/lib/Plugins.Essentials/src/HttpEntity.cs b/lib/Plugins.Essentials/src/HttpEntity.cs index ff728e3..86f9924 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; @@ -214,13 +216,53 @@ 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, - new MemStreamWrapper(ms, (int)length) + entity: new MemStreamWrapper(in arrSeg, 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,8 +312,11 @@ namespace VNLib.Plugins.Essentials void IHttpEvent.DangerousChangeProtocol(IAlternateProtocol protocolHandler) => Entity.DangerousChangeProtocol(protocolHandler); - private sealed class MemStreamWrapper(MemoryStream memStream, int length) : IMemoryResponseReader + 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; /* @@ -280,6 +325,7 @@ namespace VNLib.Plugins.Essentials */ int read = (int)memStream.Position; + ///<inheritdoc/> public int Remaining { get @@ -289,16 +335,46 @@ namespace VNLib.Plugins.Essentials } } + ///<inheritdoc/> public void Advance(int written) => read += written; ///<inheritdoc/> public void Close() => memStream.Dispose(); - public ReadOnlyMemory<byte> GetMemory() + ///<inheritdoc/> + public ReadOnlyMemory<byte> GetMemory() => _memory.Slice(read, Remaining); + } + + + 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)stream.Position; + + ///<inheritdoc/> + public int Remaining { - byte[] intBuffer = memStream.GetBuffer(); - return new ReadOnlyMemory<byte>(intBuffer, read, Remaining); + get + { + Debug.Assert(length - read >= 0); + return length - read; + } } + + ///<inheritdoc/> + public void Advance(int written) => read += written; + + ///<inheritdoc/> + public void Close() => stream.Dispose(); + + ///<inheritdoc/> + 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/Sessions/SessionBase.cs b/lib/Plugins.Essentials/src/Sessions/SessionBase.cs index 47079a0..5f9b25d 100644 --- a/lib/Plugins.Essentials/src/Sessions/SessionBase.cs +++ b/lib/Plugins.Essentials/src/Sessions/SessionBase.cs @@ -87,7 +87,7 @@ namespace VNLib.Plugins.Essentials.Sessions get { //try to parse the IP address, otherwise return null - _ = IPAddress.TryParse(this[IP_ADDRESS_ENTRY], out IPAddress? ip); + _ = IPAddress.TryParse(this[IP_ADDRESS_ENTRY], out IPAddress ip); return ip; } protected set diff --git a/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs b/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs index 2edb30c..eccfdfc 100644 --- a/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs +++ b/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs @@ -65,9 +65,7 @@ namespace VNLib.Plugins.Essentials.Sessions { None = 0x00, IsSet = 0x01, - IpMatch = 0x02, - IsCrossOrigin = 0x04, - CrossOriginMatch = 0x08, + IpMatch = 0x02 } private readonly ISession UserSession; @@ -117,24 +115,6 @@ namespace VNLib.Plugins.Essentials.Sessions } /// <summary> - /// If the current connection and stored session have matching cross origin domains - /// </summary> - public readonly bool CrossOriginMatch - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _flags.HasFlag(SessionFlags.CrossOriginMatch); - } - - /// <summary> - /// Was the original session cross origin? - /// </summary> - public readonly bool CrossOrigin - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _flags.HasFlag(SessionFlags.IsCrossOrigin); - } - - /// <summary> /// Was this session just created on this connection? /// </summary> public readonly bool IsNew @@ -252,10 +232,10 @@ namespace VNLib.Plugins.Essentials.Sessions { UserSession = session; - SessionFlags flags = SessionFlags.IsSet; + _flags |= SessionFlags.IsSet; //Set ip match flag if current ip and stored ip match - flags |= trueIp.Equals(session.UserIP) ? SessionFlags.IpMatch : SessionFlags.None; + _flags |= trueIp.Equals(session.UserIP) ? SessionFlags.IpMatch : SessionFlags.None; //If the session is new, we can store intial security variables if (session.IsNew) @@ -266,8 +246,6 @@ namespace VNLib.Plugins.Essentials.Sessions UserAgent = ci.UserAgent; SpecifiedOrigin = ci.Origin; SecurityProcol = ci.GetSslProtocol(); - - flags |= ci.CrossOrigin ? SessionFlags.IsCrossOrigin : SessionFlags.None; } else { @@ -275,15 +253,7 @@ namespace VNLib.Plugins.Essentials.Sessions UserAgent = session.GetUserAgent(); SpecifiedOrigin = session.GetOriginUri(); SecurityProcol = session.GetSecurityProtocol(); - - flags |= session.IsCrossOrigin() ? SessionFlags.IsCrossOrigin : SessionFlags.None; } - - //Set cross origin orign match flags, if the stored origin, and connection origin - flags |= ci.Origin != null && ci.Origin.Equals(SpecifiedOrigin) ? SessionFlags.CrossOriginMatch : SessionFlags.None; - - //store flags - _flags = flags; } ///<inheritdoc/> 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; } |