From 904560a7b5eafd7580fb0a03e778d1751e72a503 Mon Sep 17 00:00:00 2001 From: vnugent Date: Thu, 1 Aug 2024 21:13:04 -0400 Subject: build(app): swallow vnlib.webserver into core & build updates --- .../src/Compression/FallbackCompressionManager.cs | 144 +++++++++++++++++++++ .../src/Compression/HttpCompressor.cs | 123 ++++++++++++++++++ 2 files changed, 267 insertions(+) create mode 100644 apps/VNLib.WebServer/src/Compression/FallbackCompressionManager.cs create mode 100644 apps/VNLib.WebServer/src/Compression/HttpCompressor.cs (limited to 'apps/VNLib.WebServer/src/Compression') diff --git a/apps/VNLib.WebServer/src/Compression/FallbackCompressionManager.cs b/apps/VNLib.WebServer/src/Compression/FallbackCompressionManager.cs new file mode 100644 index 0000000..d2eb719 --- /dev/null +++ b/apps/VNLib.WebServer/src/Compression/FallbackCompressionManager.cs @@ -0,0 +1,144 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.WebServer +* File: FallbackCompressionManager.cs +* +* FallbackCompressionManager.cs is part of VNLib.WebServer which is part +* of the larger VNLib collection of libraries and utilities. +* +* VNLib.WebServer is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.WebServer 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 +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.WebServer. If not, see http://www.gnu.org/licenses/. +*/ + +using System; +using System.Buffers; +using System.Diagnostics; +using System.IO.Compression; + +using VNLib.Net.Http; + +namespace VNLib.WebServer.Compression +{ + + /* + * The fallback compression manager is used when the user did not configure a + * compression manager library. Since .NET only exposes a brotli encoder, that + * is not a stream api, (gzip and deflate are stream api's) Im only supporting + * brotli for now. This is better than nothing lol + */ + + + internal sealed class FallbackCompressionManager : IHttpCompressorManager + { + /// + public object AllocCompressor() => new BrCompressorState(); + + /// + public CompressionMethod GetSupportedMethods() => CompressionMethod.Brotli; + + /// + public int InitCompressor(object compressorState, CompressionMethod compMethod) + { + BrCompressorState compressor = (BrCompressorState)compressorState; + ref BrotliEncoder encoder = ref compressor.GetEncoder(); + + //Init new brotli encoder struct + encoder = new(9, 24); + return 0; + } + + /// + public void DeinitCompressor(object compressorState) + { + BrCompressorState compressor = (BrCompressorState)compressorState; + ref BrotliEncoder encoder = ref compressor.GetEncoder(); + + //Clean up the encoder + encoder.Dispose(); + encoder = default; + } + + /// + public CompressionResult CompressBlock(object compressorState, ReadOnlyMemory input, Memory output) + { + //Output buffer should never be empty, server guards this + Debug.Assert(!output.IsEmpty, "Exepcted a non-zero length output buffer"); + + BrCompressorState compressor = (BrCompressorState)compressorState; + ref BrotliEncoder encoder = ref compressor.GetEncoder(); + + //Compress the supplied block + OperationStatus status = encoder.Compress(input.Span, output.Span, out int bytesConsumed, out int bytesWritten, false); + + /* + * Should always return done, because the output buffer is always + * large enough and that data/state cannot be invalid + */ + Debug.Assert(status == OperationStatus.Done); + + return new() + { + BytesRead = bytesConsumed, + BytesWritten = bytesWritten, + }; + } + + /// + public int Flush(object compressorState, Memory output) + { + OperationStatus status; + + //Output buffer should never be empty, server guards this + Debug.Assert(!output.IsEmpty, "Exepcted a non-zero length output buffer"); + + BrCompressorState compressor = (BrCompressorState)compressorState; + ref BrotliEncoder encoder = ref compressor.GetEncoder(); + + /* + * A call to compress with the isFinalBlock flag set to true will + * cause a BROTLI_OPERATION_FINISH operation to be performed. This is + * actually the proper way to complete a brotli compression stream. + * + * See vnlib_compress project for more details. + */ + status = encoder.Compress( + source: default, + destination: output.Span, + bytesConsumed: out _, + bytesWritten: out int bytesWritten, + isFinalBlock: true + ); + + /* + * Function can return Done or DestinationTooSmall if there is still more data + * stored in the compressor to be written. If InvaliData is returned, then there + * is a problem with the encoder state or the output buffer, this condition should + * never happen. + */ + Debug.Assert(status != OperationStatus.InvalidData, $"Failed with status {status}, written {bytesWritten}, buffer size {output.Length}"); + + //Return the number of bytes actually accumulated + return bytesWritten; + } + + + private sealed class BrCompressorState + { + private BrotliEncoder _encoder; + + public ref BrotliEncoder GetEncoder() => ref _encoder; + } + } +} diff --git a/apps/VNLib.WebServer/src/Compression/HttpCompressor.cs b/apps/VNLib.WebServer/src/Compression/HttpCompressor.cs new file mode 100644 index 0000000..beda67b --- /dev/null +++ b/apps/VNLib.WebServer/src/Compression/HttpCompressor.cs @@ -0,0 +1,123 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.WebServer +* File: HttpCompressor.cs +* +* HttpCompressor.cs is part of VNLib.WebServer which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.WebServer is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.WebServer 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 +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.WebServer. If not, see http://www.gnu.org/licenses/. +*/ + +using System; +using System.IO; +using System.Text.Json; +using System.Reflection; +using System.Runtime.Loader; + +using VNLib.Utils.IO; +using VNLib.Utils.Logging; +using VNLib.Utils.Resources; +using VNLib.Net.Http; + +using VNLib.WebServer.Config; +using VNLib.WebServer.RuntimeLoading; +using VNLib.WebServer.Config.Model; + +namespace VNLib.WebServer.Compression +{ + + internal static class HttpCompressor + { + /* + * A function delegate that is invoked on the user-defined http compressor library + * when loaded + */ + private delegate void OnHttpLibLoad(ILogProvider log, JsonElement? configData); + + /// + /// Attempts to load a user-defined http compressor library from the specified path in the config, + /// otherwise falls back to the default http compressor, unless the command line disabled compression. + /// + /// Process wide- argument list + /// The top-level config element + /// The application logger to write logging events to + /// The that the user configured, or null if disabled + public static IHttpCompressorManager? LoadOrDefaultCompressor(ProcessArguments args, HttpCompressorConfig compConfig, IServerConfig config, ILogProvider logger) + { + const string EXTERN_LIB_LOAD_METHOD_NAME = "OnLoad"; + + if (args.HasArgument("--compression-off")) + { + logger.Information("Http compression disabled by cli args"); + return null; + } + + if(!compConfig.Enabled) + { + logger.Information("Http compression disabled by config"); + return null; + } + + if (string.IsNullOrWhiteSpace(compConfig.AssemblyPath)) + { + logger.Information("Falling back to default http compressor"); + return new FallbackCompressionManager(); + } + + //Make sure the file exists + if (!FileOperations.FileExists(compConfig.AssemblyPath)) + { + logger.Warn("The specified http compressor assembly file does not exist, falling back to default http compressor"); + return new FallbackCompressionManager(); + } + + //Try to load the assembly into our process alc, we dont need to worry about unloading + ManagedLibrary lib = ManagedLibrary.LoadManagedAssembly(compConfig.AssemblyPath, AssemblyLoadContext.Default); + + logger.Debug("Loading user defined compressor assembly: {asm}", Path.GetFileName(lib.AssemblyPath)); + + try + { + //Load the compressor manager type from the assembly + IHttpCompressorManager instance = lib.LoadTypeFromAssembly(); + + /* + * We can provide some optional library initialization functions if the library + * supports it. First we can allow the library to write logs to our log provider + * and second we can provide the library with the raw configuration data as a byte array + */ + + //Invoke the on load method with the logger and config data + OnHttpLibLoad? onlibLoadConfig = ManagedLibrary.TryGetMethod(instance, EXTERN_LIB_LOAD_METHOD_NAME); + onlibLoadConfig?.Invoke(logger, config.GetDocumentRoot()); + + //Invoke parameterless on load method + Action? onLibLoad = ManagedLibrary.TryGetMethod(instance, EXTERN_LIB_LOAD_METHOD_NAME); + onLibLoad?.Invoke(); + + logger.Information("Custom compressor library loaded"); + + return instance; + } + //Catch TIE and throw the inner exception for cleaner debug + catch (TargetInvocationException te) when (te.InnerException != null) + { + throw te.InnerException; + } + } + } +} -- cgit