Skip to content

Commit

Permalink
Add support for Zstd compression (#518)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kaliumhexacyanoferrat authored Oct 11, 2024
1 parent 5b35a47 commit 2547873
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 37 deletions.
26 changes: 25 additions & 1 deletion Modules/Compression/CompressedContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,48 @@ public static class CompressedContent

#region Builder

/// <summary>
/// Creates an empty builder that can be configured by adding
/// compression algorithms.
/// </summary>
/// <returns>The newly created builder</returns>
public static CompressionConcernBuilder Empty() => new();

/// <summary>
/// Creates a pre-configured builder which already supports
/// Zstandard, Brotli and Gzip compression.
/// </summary>
/// <returns>The newly created builder</returns>
public static CompressionConcernBuilder Default()
{
return new CompressionConcernBuilder().Add(new BrotliCompression())
return new CompressionConcernBuilder().Add(new ZstdCompression())
.Add(new BrotliCompression())
.Add(new GzipAlgorithm());
}

#endregion

#region Extensions

/// <summary>
/// Configures the host to compress responses if the client supports
/// this feature.
/// </summary>
/// <param name="host">The host to be configured</param>
/// <param name="compression">The compression builder to use for compression</param>
/// <returns>The configured server host for chaining</returns>
public static IServerHost Compression(this IServerHost host, CompressionConcernBuilder compression)
{
host.Add(compression);
return host;
}

/// <summary>
/// Configures the host to compress responses if the client supports
/// this feature.
/// </summary>
/// <param name="host">The host to be configured</param>
/// <returns>The configured server host for chaining</returns>
public static IServerHost Compression(this IServerHost host) => host.Compression(Default());

#endregion
Expand Down
2 changes: 2 additions & 0 deletions Modules/Compression/GenHTTP.Modules.Compression.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@

<ProjectReference Include="..\..\API\GenHTTP.Api.csproj" />

<PackageReference Include="ZstdSharp.Port" Version="0.8.1" />

<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />

</ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Modules/Compression/Providers/BrotliCompression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public sealed class BrotliCompression : ICompressionAlgorithm

public string Name => "br";

public Priority Priority => Priority.High;
public Priority Priority => Priority.Medium;

public IResponseContent Compress(IResponseContent content, CompressionLevel level)
{
Expand Down
35 changes: 35 additions & 0 deletions Modules/Compression/Providers/ZstdCompression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.IO.Compression;

using GenHTTP.Api.Content.IO;
using GenHTTP.Api.Infrastructure;
using GenHTTP.Api.Protocol;

using ZstdSharp;

namespace GenHTTP.Modules.Compression.Providers;

public sealed class ZstdCompression : ICompressionAlgorithm
{

public string Name => "zstd";

public Priority Priority => Priority.High;

public IResponseContent Compress(IResponseContent content, CompressionLevel level)
{
return new CompressedResponseContent(content, (target) => new CompressionStream(target, level: MapLevel(level), leaveOpen: false));
}

private static int MapLevel(CompressionLevel level)
{
return level switch
{
CompressionLevel.Fastest => 1,
CompressionLevel.SmallestSize => 22,
CompressionLevel.Optimal => 3,
_ => throw new InvalidOperationException($"Unable to map compression level {level}")
};
}

}
40 changes: 20 additions & 20 deletions Packages/GenHTTP.Core.nuspec
Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">

<metadata>

<id>GenHTTP.Core</id>
<version>9.0.0</version>

<description>Basic dependencies for projects using the GenHTTP framework, including the engine itself</description>
<copyright></copyright>

<projectUrl>https://genhttp.org/</projectUrl>

<authors>Andreas Nägeli</authors>
<tags>HTTP Webserver Library C# Server Embeddable Embedded Host</tags>
<tags>HTTP Webserver Library C# Server REST API Embeddable Embedded Host</tags>

<icon>images\icon.png</icon>

<license type="file">LICENSE</license>
<requireLicenseAcceptance>false</requireLicenseAcceptance>

<dependencies>

<group>

<dependency id="GenHTTP.Api" version="9.0.0" />

<dependency id="GenHTTP.Engine" version="9.0.0" />
<dependency id="GenHTTP.Modules.Practices" version="9.0.0" />

<dependency id="GenHTTP.Modules.Practices" version="9.0.0" />
<dependency id="GenHTTP.Modules.Basics" version="9.0.0" />
<dependency id="GenHTTP.Modules.Layouting" version="9.0.0" />

</group>

</dependencies>

</metadata>

<files>
<file src="..\Resources\icon.png" target="images\" />
<file src="..\LICENSE" target="LICENSE" />
</files>
</package>

</package>
12 changes: 6 additions & 6 deletions Playground/GenHTTP.Playground.csproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>

<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>

<LangVersion>13.0</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

</PropertyGroup>

<ItemGroup>
Expand Down Expand Up @@ -39,7 +39,7 @@
<ProjectReference Include="..\Modules\StaticWebsites\GenHTTP.Modules.StaticWebsites.csproj" />
<ProjectReference Include="..\Modules\VirtualHosting\GenHTTP.Modules.VirtualHosting.csproj" />
<ProjectReference Include="..\Modules\Webservices\GenHTTP.Modules.Webservices.csproj" />

</ItemGroup>

</Project>
19 changes: 11 additions & 8 deletions Testing/Acceptance/Engine/CompressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,28 +53,31 @@ public async Task TestCompression()
using var runner = TestHost.Run(Layout.Create());

var request = runner.GetRequest();
request.Headers.Add("Accept-Encoding", "gzip, br");
request.Headers.Add("Accept-Encoding", "gzip, br, zstd");

using var response = await runner.GetResponseAsync(request);

Assert.AreEqual("br", response.Content.Headers.ContentEncoding.First());
Assert.AreEqual("zstd", response.Content.Headers.ContentEncoding.First());
}

/// <summary>
/// As a browser, I expect only supported compression algorithms to be used
/// to generate my response.
/// </summary>
[TestMethod]
public async Task TestSpecficAlgorithm()
public async Task TestSpecificAlgorithms()
{
using var runner = TestHost.Run(Layout.Create());
foreach (var algorithm in new[] { "gzip", "br", "zstd" })
{
using var runner = TestHost.Run(Layout.Create());

var request = runner.GetRequest();
request.Headers.Add("Accept-Encoding", "gzip");
var request = runner.GetRequest();
request.Headers.Add("Accept-Encoding", algorithm);

using var response = await runner.GetResponseAsync(request);
using var response = await runner.GetResponseAsync(request);

Assert.AreEqual("gzip", response.Content.Headers.ContentEncoding.First());
Assert.AreEqual(algorithm, response.Content.Headers.ContentEncoding.First());
}
}

/// <summary>
Expand Down
3 changes: 2 additions & 1 deletion Testing/Testing/TestHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ public static HttpClient GetClient(bool ignoreSecurityErrors = false, bool follo
var handler = new HttpClientHandler
{
AllowAutoRedirect = followRedirects,
Credentials = creds
Credentials = creds,
UseProxy = false
};

if (cookies != null)
Expand Down

0 comments on commit 2547873

Please sign in to comment.