Skip to content

Commit 9c936f3

Browse files
authored
Add deflate compression to .NET 6 (#1548)
1 parent 6acecf4 commit 9c936f3

File tree

15 files changed

+240
-67
lines changed

15 files changed

+240
-67
lines changed

src/Grpc.AspNetCore.Server/Grpc.AspNetCore.Server.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
<ItemGroup>
1717
<Compile Include="..\Shared\CommonGrpcProtocolHelpers.cs" Link="Internal\CommonGrpcProtocolHelpers.cs" />
18+
<Compile Include="..\Shared\NonDisposableMemoryStream.cs" Link="Internal\NonDisposableMemoryStream.cs" />
1819
<Compile Include="..\Shared\DefaultDeserializationContext.cs" Link="Internal\DefaultDeserializationContext.cs" />
1920
<Compile Include="..\Shared\Server\BindMethodFinder.cs" Link="Model\Internal\BindMethodFinder.cs" />
2021
<Compile Include="..\Shared\Server\ClientStreamingServerMethodInvoker.cs" Link="Model\Internal\ClientStreamingServerMethodInvoker.cs" />

src/Grpc.AspNetCore.Server/Internal/GrpcServiceOptionsSetup.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ public void Configure(GrpcServiceOptions options)
3939
if (options._compressionProviders == null || options._compressionProviders.Count == 0)
4040
{
4141
options.CompressionProviders.Add(new GzipCompressionProvider(CompressionLevel.Fastest));
42-
// deflate is not supported. .NET's DeflateStream does not support RFC1950 - https://github.com/dotnet/corefx/issues/7570
42+
#if NET6_0_OR_GREATER
43+
options.CompressionProviders.Add(new DeflateCompressionProvider(CompressionLevel.Fastest));
44+
#endif
4345
}
4446
}
4547
}

src/Grpc.AspNetCore.Server/Internal/HttpContextSerializationContext.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
using System.Runtime.CompilerServices;
2424
using Grpc.Core;
2525
using Grpc.Net.Compression;
26+
using Grpc.Shared;
2627

2728
namespace Grpc.AspNetCore.Server.Internal
2829
{
@@ -213,7 +214,7 @@ private ReadOnlySpan<byte> CompressMessage(ReadOnlySpan<byte> messageData)
213214

214215
GrpcServerLog.CompressingMessage(_serverCallContext.Logger, _compressionProvider.EncodingName);
215216

216-
var output = new MemoryStream();
217+
var output = new NonDisposableMemoryStream();
217218

218219
// Compression stream must be disposed before its content is read.
219220
// GZipStream writes final Adler32 at the end of the stream on dispose.

src/Grpc.Net.Client/Grpc.Net.Client.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<Compile Include="..\Shared\NullableAttributes.cs" Link="Internal\NullableAttributes.cs" />
3333
<Compile Include="..\Shared\Http2ErrorCode.cs" Link="Internal\Http2ErrorCode.cs" />
3434
<Compile Include="..\Shared\Http3ErrorCode.cs" Link="Internal\Http3ErrorCode.cs" />
35+
<Compile Include="..\Shared\NonDisposableMemoryStream.cs" Link="Internal\NonDisposableMemoryStream.cs" />
3536
</ItemGroup>
3637

3738
<ItemGroup>

src/Grpc.Net.Client/Internal/GrpcCallSerializationContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ private ReadOnlySpan<byte> CompressMessage(ReadOnlySpan<byte> messageData)
278278

279279
GrpcCallLog.CompressingMessage(_call.Logger, _compressionProvider.EncodingName);
280280

281-
var output = new MemoryStream();
281+
var output = new NonDisposableMemoryStream();
282282

283283
// Compression stream must be disposed before its content is read.
284284
// GZipStream writes final Adler32 at the end of the stream on dispose.

src/Grpc.Net.Client/Internal/GrpcProtocolConstants.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ internal static class GrpcProtocolConstants
5252
internal static readonly Dictionary<string, ICompressionProvider> DefaultCompressionProviders = new Dictionary<string, ICompressionProvider>(StringComparer.Ordinal)
5353
{
5454
["gzip"] = new GzipCompressionProvider(System.IO.Compression.CompressionLevel.Fastest),
55-
// deflate is not supported. .NET's DeflateStream does not support RFC1950 - https://github.com/dotnet/corefx/issues/7570
55+
#if NET6_0_OR_GREATER
56+
["deflate"] = new DeflateCompressionProvider(System.IO.Compression.CompressionLevel.Fastest),
57+
#endif
5658
};
5759

5860
internal const int MessageDelimiterSize = 4; // how many bytes it takes to encode "Message-Length"
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#region Copyright notice and license
2+
3+
// Copyright 2019 The gRPC Authors
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
#endregion
18+
19+
#if NET6_0_OR_GREATER
20+
using System.IO;
21+
using System.IO.Compression;
22+
23+
namespace Grpc.Net.Compression
24+
{
25+
/// <summary>
26+
/// Deflate compression provider.
27+
/// </summary>
28+
public sealed class DeflateCompressionProvider : ICompressionProvider
29+
{
30+
private readonly CompressionLevel _defaultCompressionLevel;
31+
32+
/// <summary>
33+
/// Initializes a new instance of the <see cref="DeflateCompressionProvider"/> class with the specified <see cref="CompressionLevel"/>.
34+
/// </summary>
35+
/// <param name="defaultCompressionLevel">The default compression level to use when compressing data.</param>
36+
public DeflateCompressionProvider(CompressionLevel defaultCompressionLevel)
37+
{
38+
_defaultCompressionLevel = defaultCompressionLevel;
39+
}
40+
41+
/// <summary>
42+
/// The encoding name used in the 'grpc-encoding' and 'grpc-accept-encoding' request and response headers.
43+
/// </summary>
44+
public string EncodingName => "deflate";
45+
46+
/// <summary>
47+
/// Create a new compression stream.
48+
/// </summary>
49+
/// <param name="stream">The stream that compressed data is written to.</param>
50+
/// <param name="compressionLevel">The compression level.</param>
51+
/// <returns>A stream used to compress data.</returns>
52+
public Stream CreateCompressionStream(Stream stream, CompressionLevel? compressionLevel)
53+
{
54+
// As described in RFC 2616, the deflate content-coding is actually
55+
// the "zlib" format (RFC 1950) in combination with the "deflate"
56+
// compression algrithm (RFC 1951). So while potentially
57+
// counterintuitive based on naming, this needs to use ZLibStream
58+
// rather than DeflateStream.
59+
return new ZLibStream(stream, compressionLevel ?? _defaultCompressionLevel);
60+
}
61+
62+
/// <summary>
63+
/// Create a new decompression stream.
64+
/// </summary>
65+
/// <param name="stream">The stream that compressed data is copied from.</param>
66+
/// <returns>A stream used to decompress data.</returns>
67+
public Stream CreateDecompressionStream(Stream stream)
68+
{
69+
// As described in RFC 2616, the deflate content-coding is actually
70+
// the "zlib" format (RFC 1950) in combination with the "deflate"
71+
// compression algrithm (RFC 1951). So while potentially
72+
// counterintuitive based on naming, this needs to use ZLibStream
73+
// rather than DeflateStream.
74+
return new ZLibStream(stream, CompressionMode.Decompress);
75+
}
76+
}
77+
}
78+
#endif

src/Grpc.Net.Common/Compression/GzipCompressionProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ namespace Grpc.Net.Compression
2323
/// <summary>
2424
/// GZIP compression provider.
2525
/// </summary>
26-
public class GzipCompressionProvider : ICompressionProvider
26+
public sealed class GzipCompressionProvider : ICompressionProvider
2727
{
2828
private readonly CompressionLevel _defaultCompressionLevel;
2929

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#region Copyright notice and license
2+
3+
// Copyright 2019 The gRPC Authors
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
#endregion
18+
19+
namespace Grpc.Shared
20+
{
21+
internal sealed class NonDisposableMemoryStream : MemoryStream
22+
{
23+
protected override void Dispose(bool disposing)
24+
{
25+
// Ignore dispose from wrapping compression stream.
26+
// If MemoryStream is disposed then Length isn't available.
27+
}
28+
}
29+
}

test/FunctionalTests/Client/CompressionTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public class CompressionTests : FunctionalTestBase
3131
{
3232
[TestCase("identity")]
3333
[TestCase("gzip")]
34+
#if NET6_0_OR_GREATER
35+
[TestCase("deflate")]
36+
#endif
3437
public async Task SendCompressedMessage_ServiceCompressionConfigured_ResponseGzipEncoding(string algorithmName)
3538
{
3639
// Arrange

0 commit comments

Comments
 (0)