aboutsummaryrefslogtreecommitdiff
path: root/apps/VNLib.WebServer/src/Compression
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-08-01 21:13:04 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2024-08-01 21:13:04 -0400
commit904560a7b5eafd7580fb0a03e778d1751e72a503 (patch)
tree9ffc07d9f9dd6a9106b8cd695a6caa591aac8e95 /apps/VNLib.WebServer/src/Compression
parent6af95e61212611908d39235222474d4038e10fcd (diff)
build(app): swallow vnlib.webserver into core & build updates
Diffstat (limited to 'apps/VNLib.WebServer/src/Compression')
-rw-r--r--apps/VNLib.WebServer/src/Compression/FallbackCompressionManager.cs144
-rw-r--r--apps/VNLib.WebServer/src/Compression/HttpCompressor.cs123
2 files changed, 267 insertions, 0 deletions
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
+ {
+ /// <inheritdoc/>
+ public object AllocCompressor() => new BrCompressorState();
+
+ /// <inheritdoc/>
+ public CompressionMethod GetSupportedMethods() => CompressionMethod.Brotli;
+
+ /// <inheritdoc/>
+ 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;
+ }
+
+ /// <inheritdoc/>
+ public void DeinitCompressor(object compressorState)
+ {
+ BrCompressorState compressor = (BrCompressorState)compressorState;
+ ref BrotliEncoder encoder = ref compressor.GetEncoder();
+
+ //Clean up the encoder
+ encoder.Dispose();
+ encoder = default;
+ }
+
+ /// <inheritdoc/>
+ public CompressionResult CompressBlock(object compressorState, ReadOnlyMemory<byte> input, Memory<byte> 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,
+ };
+ }
+
+ /// <inheritdoc/>
+ public int Flush(object compressorState, Memory<byte> 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);
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="args">Process wide- argument list</param>
+ /// <param name="config">The top-level config element</param>
+ /// <param name="logger">The application logger to write logging events to</param>
+ /// <returns>The <see cref="IHttpCompressorManager"/> that the user configured, or null if disabled</returns>
+ 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<IHttpCompressorManager>();
+
+ /*
+ * 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<OnHttpLibLoad>(instance, EXTERN_LIB_LOAD_METHOD_NAME);
+ onlibLoadConfig?.Invoke(logger, config.GetDocumentRoot());
+
+ //Invoke parameterless on load method
+ Action? onLibLoad = ManagedLibrary.TryGetMethod<Action>(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;
+ }
+ }
+ }
+}