Reuse HttpMessageHandler with multiple Grpc clients

196 views Asked by At

I have multiple Grpc clients and each need to pack a client certificate. I am using Grpc.Net.ClientFactory to register all my clients.

Not understanding how HttpMessageHandler should be reused, I am concerned about creating a new HttpMessageHandler for each Grpc client being registered as Microsoft docs indicates that

each handler typically manages its own underlying HTTP connections; creating more handlers than necessary can result in connection delays

// register serviceAClient
builder.Services.AddGrpcClient<ServiceA.ServiceAClient>(o => o.Address = new Uri(serviceUrl))
    .ConfigureChannel(o => o.HttpHandler = GetHttpHandler(certThumbprint));
        
// register serviceBClient
builder.Services.AddGrpcClient<ServiceB.ServiceBClient>(o => o.Address = new Uri(serviceUrl))
    .ConfigureChannel(o => o.HttpHandler = GetHttpHandler(certThumbprint));

// creates a new HttpMessageHandler 
private static HttpMessageHandler GetHttpHandler(string certThumbprint)
{
    var httpHandler = new SocketsHttpHandler()
    {
        PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
        KeepAlivePingDelay = TimeSpan.FromSeconds(60),
        KeepAlivePingTimeout = TimeSpan.FromSeconds(30),
        EnableMultipleHttp2Connections = true,
        SslOptions = new SslClientAuthenticationOptions
        {
            ClientCertificates = LoadClientCertificates(certThumbprint)
        };
    
    return httpHandler;
}

What is the better way to deal with HttpMessageHandler in multiple Grpc clients scenario - should each client be registered with a single HttpMessageHandler? Or each client should have its own HttpMessageHandler like in the example above? What are the trade-offs here?

2

There are 2 answers

0
Arpit Khandelwal On

I ended up "modifying" the HttpMessageHandler instead of adding/replacing one.

This approach feels better as its not invasive in how Grpc.Net.ClientFactory is taking care of the grpcChannel and the underlying http connections and that default chain of HttpMessageHandler is preserved.

This approach however assumes that the final http handler is always the SocketsHttpHandler.

services.AddGrpcClient<ServiceA.ServiceAClient>(o => o.Address = new Uri(serviceUrl))
    .ConfigureChannel(o => o.HttpHandler.ConfigureHttpHandler(clientCertificateThumbprint));

services.AddGrpcClient<ServiceB.ServiceBClient>(o => o.Address = new Uri(serviceUrl))
    .ConfigureChannel(o => o.HttpHandler.ConfigureHttpHandler(clientCertificateThumbprint));
    
private static void ConfigureHttpHandler(this HttpMessageHandler? handler, string certificateThumbprint)
{
    // find the SocketsHttpHandler and configure it
    while (handler != null)
    {
        if (handler is DelegatingHandler delegatingHandler)
        {
            handler = delegatingHandler.InnerHandler;
        }
        else if (handler is SocketsHttpHandler socketsHttpHandler)
        {
            socketsHttpHandler.PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan;
            socketsHttpHandler.KeepAlivePingDelay = TimeSpan.FromSeconds(60);
            socketsHttpHandler.KeepAlivePingTimeout = TimeSpan.FromSeconds(30);
            socketsHttpHandler.EnableMultipleHttp2Connections = true;

            if (!string.IsNullOrEmpty(certificateThumbprint))
            {
                socketsHttpHandler.SslOptions = new SslClientAuthenticationOptions
                {
                    ClientCertificates = LoadClientCertificates(certificateThumbprint)
                };
            }

            break;
        }
        else
        {
            log($"Unexpected HttpMessageHandler of type {handler.GetType()}.");
            break;
        }
    }
}
1
Qiang Fu On

According to this doc https://learn.microsoft.com/en-us/aspnet/core/grpc/performance?view=aspnetcore-8.0#reuse-grpc-channels.

A gRPC channel should be reused when making gRPC calls. Reusing a channel allows calls to be multiplexed through an existing HTTP/2 connection.
Multiple gRPC clients can be created from a channel, including different types of clients.

It is better to reuse same channel for multiple clients.

var channel = GrpcChannel.ForAddress(new Uri(serviceUrl), new GrpcChannelOptions
{
    HttpHandler = new SocketsHttpHandler
    {
        SslOptions = new SslClientAuthenticationOptions
        {
            ClientCertificates = LoadClientCertificates(certThumbprint)
        },
        ...   
    }
});
builder.Services.AddGrpcClient<ServiceA.ServiceAClient>(o => new ServiceA.ServiceAClient(channel));
builder.Services.AddGrpcClient<ServiceB.ServiceBClient>(o => new ServiceB.ServiceBClient(channel));