SHARE:

ASP.NET Core 6: Bring your custom compression provider in gRPC

Introduction

The compression of data during their transport on the Internet or a network makes it possible to gain significantly in terms of performance. If gRPC is already efficient thanks to the payloads transported in binary via HTTP/2, it is possible to improve its performance a little more. It is possible to use Gzip compression quite easily with gRPC, however if you plan to use another compression algorithm, such as Brotli, you will have to implement it yourself and that is what I will show you in this article.

Implementing Brotli compression provider

Implementing a compression provider for gRPC requires inheriting from a specific interface: ICompressionProvider. This interface is in the Grpc.Net.Compression namespace and has two functions and one property:

using System.IO.Compression;
namespace Grpc.Net.Compression
{
/// <summary>
/// Provides a specific compression implementation to compress gRPC messages.
/// </summary>
public interface ICompressionProvider
{
/// <summary>
/// The encoding name used in the 'grpc-encoding' and 'grpc-accept-encoding' request and response headers.
/// </summary>
string EncodingName { get; }
/// <summary>
/// Create a new compression stream.
/// </summary>
/// <param name="stream">The stream that compressed data is written to.</param>
/// <param name="compressionLevel">The compression level.</param>
/// <returns>A stream used to compress data.</returns>
Stream CreateCompressionStream(Stream stream, CompressionLevel? compressionLevel);
/// <summary>
/// Create a new decompression stream.
/// </summary>
/// <param name="stream">The stream that compressed data is copied from.</param>
/// <returns>A stream used to decompress data.</returns>
Stream CreateDecompressionStream(Stream stream);
}
}

This element being identified we can now implement our compression class. We will use Brotli as the compression provider. to do this, we will rely on the BrotliStream class, belonging to the System.IO.Compression namespace. Then we can implement our compression function, decompression and define the name of our encoder. They are here about “br” which defines the use of Brotli as compression algorithm in the headers, “grpc-accept-encoding” more exactly:

using Grpc.Net.Compression;
using System.IO;
using System.IO.Compression;
namespace DemogRPC.Web.Compression;
public class BrotliCompressionProvider : ICompressionProvider
{
private readonly CompressionLevel? _compressionLevel;
public BrotliCompressionProvider(CompressionLevel compressionLevel)
{
_compressionLevel = compressionLevel;
}
public BrotliCompressionProvider()
{
}
public string EncodingName => "br"; // Must match grpc-accept-encoding
public Stream CreateCompressionStream(Stream outputStream, CompressionLevel? compressionLevel)
{
if (_compressionLevel.HasValue)
return new BrotliStream(outputStream, compressionLevel ?? _compressionLevel.Value, true);
else if (!_compressionLevel.HasValue && compressionLevel.HasValue)
return new BrotliStream(outputStream, compressionLevel.Value, true);
return new BrotliStream(outputStream, CompressionLevel.Fastest, true);
}
public Stream CreateDecompressionStream(Stream stream)
{
return new BrotliStream(stream, CompressionMode.Decompress);
}
}

That’s pretty straightforward as you can see.

Enabling Brotli compression on gRPC

As you can imagine, for the compression to work, the client must be setup to compress and decompress the messages sent and received to/from the server. Same thing on the server side of course. Let’s start with the server:

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddGrpc(options => {
options.CompressionProviders = new List<ICompressionProvider>
{
new BrotliCompressionProvider() // br
};
options.ResponseCompressionAlgorithm = "br"; // grpc-accept-encoding
options.ResponseCompressionLevel = CompressionLevel.Optimal; // compression level used if not set on the provider
});
var app = builder.Build();
// ....
// Run the app
app.Run();

You may have noticed CompressionProviders option takes a list of compression provider. It’s means you can implement several compression algorithm on your server, and, depending on what the client is requesting, the server can select the right compression to apply. This is driven by the “grpc-accept-encoding” sent by the client.

Client-side it’s simpler you simply need to enable Brotli like this:

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddGrpcClient<CountryServiceClient>(o =>
{
o.Address = new Uri(builder.Configuration.GetSection("CountryServiceUri").Value);
})
.ConfigureChannel(o =>
{
o.CompressionProviders = new List<ICompressionProvider>
{
new BrotliCompressionProvider()
};
});
var app = builder.Build();
// ....
app.Run();

Obvisouly, there is no magical there, both side needs to reference the compression provider implementation (if you are implementing both).

That’s it! Since the client enables Brotli compression, it will use it automatically.

Demo

After creating a client and a server gRPC app and you enable Brotli you should now see in the log (server-side) the following when the server send the response to the client:

And client-side since it receives the response from the server:

Easy isn’t it ? 🙂

Written by

anthonygiretti

Anthony is a specialist in Web technologies (14 years of experience), in particular Microsoft .NET and learns the Cloud Azure platform. He has received twice the Microsoft MVP award and he is also certified Microsoft MCSD and Azure Fundamentals.