Интеграция фабрики клиента gRPC в .NET

Автор: Джеймс Ньютон-Кинг (James Newton-King)

Интеграция gRPC с HttpClientFactory обеспечивает централизованный способ создания клиентов gRPC. Его можно использовать в качестве альтернативы настройке отдельных экземпляров клиента gRPC. Интеграция производства доступна в пакете NuGet Grpc.Net.ClientFactory.

Производство обеспечивает следующие преимущества:

  • предоставляет центральное расположение для настройки логических экземпляров клиента gRPC;
  • управляет временем существования базового объекта HttpClientMessageHandler;
  • автоматически распространяет крайний срок и отмену в службе gRPC ASP.NET Core.

Регистрация клиентов gRPC

Для регистрации клиента gRPC можно использовать универсальный метод расширения AddGrpcClient в экземпляре WebApplicationBuilder в точке входа приложения в Program.cs, указав класс типизированного клиента gRPC и адрес службы:

builder.Services.AddGrpcClient<Greeter.GreeterClient>(o =>
{
    o.Address = new Uri("https://localhost:5001");
});

Тип клиента gRPC регистрируется в системе внедрения зависимостей (DI) как временный. После этого клиент можно внедрять и использовать напрямую в типах, создаваемых посредством внедрения зависимостей. Автоматически внедрять клиенты gRPC можно в контроллеры MVC ASP.NET Core, концентраторы SignalR и службы gRPC:

public class AggregatorService : Aggregator.AggregatorBase
{
    private readonly Greeter.GreeterClient _client;

    public AggregatorService(Greeter.GreeterClient client)
    {
        _client = client;
    }

    public override async Task SayHellos(HelloRequest request,
        IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
    {
        // Forward the call on to the greeter service
        using (var call = _client.SayHellos(request))
        {
            await foreach (var response in call.ResponseStream.ReadAllAsync())
            {
                await responseStream.WriteAsync(response);
            }
        }
    }
}

Настройка HttpHandler

HttpClientFactory создает объект HttpMessageHandler, используемый клиентом gRPC. С помощью стандартных методов HttpClientFactory можно добавлять ПО промежуточного слоя для исходящих запросов или настраивать базовый обработчик HttpClientHandler объекта HttpClient:

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ClientCertificates.Add(LoadCertificate());
        return handler;
    });

Дополнительные сведения см. в статье Выполнение HTTP-запросов с помощью IHttpClientFactory.

Настройка перехватчиков

Перехватчики gRPC можно добавить к клиентам с помощью метода AddInterceptor.

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddInterceptor<LoggingInterceptor>();

Предыдущий код:

  • Регистрирует тип GreeterClient.
  • Настраивает LoggingInterceptor для этого клиента. LoggingInterceptor создается один раз и совместно используется экземплярами GreeterClient.

По умолчанию перехватчик создается один раз и совместно используется клиентами. Это поведение можно переопределить, указав область при регистрации перехватчика. Фабрику клиента можно настроить на создание нового перехватчика для каждого клиента, указав InterceptorScope.Client.

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddInterceptor<LoggingInterceptor>(InterceptorScope.Client);

Создание перехватчиков с областью действия клиента полезно в том случае, когда перехватчику требуются службы с заданной областью или временно заданной областью от внедрения зависимостей.

Для отправки Authorization метаданных с каждым запросом можно использовать перехватчик gRPC или учетные данные канала. Дополнительные сведения о настройке проверки подлинности см. в разделе Создание токена носителя с помощью фабрики клиента gRPC.

Настройка канала

Дополнительную конфигурацию можно применить к каналу с помощью метода ConfigureChannel:

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigureChannel(o =>
    {
        o.Credentials = new CustomCredentials();
    });

ConfigureChannel передается экземпляр GrpcChannelOptions. Дополнительные сведения см. в разделе Настройка параметров клиента.

Примечание.

Некоторые свойства задаются в параметре GrpcChannelOptions перед выполнением обратного вызова ConfigureChannel:

Эти значения можно переопределить.ConfigureChannel

Учетные данные вызова

Заголовок проверки подлинности можно добавить в вызовы gRPC с помощью метода AddCallCredentials:

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddCallCredentials((context, metadata) =>
    {
        if (!string.IsNullOrEmpty(_token))
        {
            metadata.Add("Authorization", $"Bearer {_token}");
        }
        return Task.CompletedTask;
    });

Дополнительные сведения о настройке проверки подлинности см. в разделе Создание токена носителя с помощью фабрики клиента gRPC.

Распространение крайнего срока и отмены

С помощью метода EnableCallContextPropagation() для клиентов gRPC, созданных производством в службе gRPC, можно настроить автоматическое распространение токена крайнего срока и отмены на дочерние вызовы. Метод расширения EnableCallContextPropagation() доступен в пакете NuGet Grpc.AspNetCore.Server.ClientFactory.

Распространение контекста вызова осуществляется путем считывания токена крайнего срока и отмены из текущего контекста запроса gRPC и его автоматического распространения на исходящие вызовы, выполняемые клиентом gRPC. Распространение контекста вызова — это отличный способ обеспечить распространение крайнего срока и отмены в случае со сложными вложенными вызовами gRPC.

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .EnableCallContextPropagation();

По умолчанию EnableCallContextPropagation вызывает ошибку, если клиент используется вне контекста вызова gRPC. Эта ошибка информирует о том, что контекст вызова для распространения отсутствует. Если вы хотите использовать клиент за пределами контекста вызова, подавите ошибку при настройке клиента с использованием SuppressContextNotFoundErrors:

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .EnableCallContextPropagation(o => o.SuppressContextNotFoundErrors = true);

Дополнительные сведения о крайних сроках и отмене RPC см. в статье Надежные службы gRPC с крайними сроками и отменой.

Именованные клиенты

Как правило, тип клиента gRPC регистрируется один раз, а затем внедряется непосредственно в конструктор типа путем внедрения зависимостей. Но есть сценарии, когда полезно иметь несколько конфигураций для одного клиента. Например, клиент, который выполняет вызовы gRPC с проверкой подлинности и без нее.

Несколько клиентов одинакового типа могут быть зарегистрированы путем присвоения каждому клиенту имени. Каждый именованный клиент может иметь собственную конфигурацию. Универсальный метод расширения AddGrpcClient имеет перегрузку, которая включает параметр имени:

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>("Greeter", o =>
    {
        o.Address = new Uri("https://localhost:5001");
    });

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>("GreeterAuthenticated", o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigureChannel(o =>
    {
        o.Credentials = new CustomCredentials();
    });

Предыдущий код:

  • Дважды регистрирует тип GreeterClient, указывая уникальное имя каждый раз.
  • Настраивает разные параметры для каждого именованного клиента. Регистрация GreeterAuthenticated добавляет учетные данные в канал, чтобы соответствующие вызовы gRPC прошли проверку подлинности.

Именованный клиент gRPC создается в коде приложения с помощью GrpcClientFactory. Тип и имя требуемого клиента указываются с помощью универсального метода GrpcClientFactory.CreateClient:

public class AggregatorService : Aggregator.AggregatorBase
{
    private readonly Greeter.GreeterClient _client;

    public AggregatorService(GrpcClientFactory grpcClientFactory)
    {
        _client = grpcClientFactory.CreateClient<Greeter.GreeterClient>("GreeterAuthenticated");
    }
}

Дополнительные ресурсы

Интеграция gRPC с HttpClientFactory обеспечивает централизованный способ создания клиентов gRPC. Его можно использовать в качестве альтернативы настройке отдельных экземпляров клиента gRPC. Интеграция производства доступна в пакете NuGet Grpc.Net.ClientFactory.

Производство обеспечивает следующие преимущества:

  • предоставляет центральное расположение для настройки логических экземпляров клиента gRPC;
  • управляет временем существования базового объекта HttpClientMessageHandler;
  • автоматически распространяет крайний срок и отмену в службе gRPC ASP.NET Core.

Регистрация клиентов gRPC

Для регистрации клиента gRPC можно использовать универсальный метод расширения AddGrpcClient в Startup.ConfigureServices, указав класс типизированного клиента gRPC и адрес службы:

services.AddGrpcClient<Greeter.GreeterClient>(o =>
{
    o.Address = new Uri("https://localhost:5001");
});

Тип клиента gRPC регистрируется в системе внедрения зависимостей (DI) как временный. После этого клиент можно внедрять и использовать напрямую в типах, создаваемых посредством внедрения зависимостей. Автоматически внедрять клиенты gRPC можно в контроллеры MVC ASP.NET Core, концентраторы SignalR и службы gRPC:

public class AggregatorService : Aggregator.AggregatorBase
{
    private readonly Greeter.GreeterClient _client;

    public AggregatorService(Greeter.GreeterClient client)
    {
        _client = client;
    }

    public override async Task SayHellos(HelloRequest request,
        IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
    {
        // Forward the call on to the greeter service
        using (var call = _client.SayHellos(request))
        {
            await foreach (var response in call.ResponseStream.ReadAllAsync())
            {
                await responseStream.WriteAsync(response);
            }
        }
    }
}

Настройка HttpHandler

HttpClientFactory создает объект HttpMessageHandler, используемый клиентом gRPC. С помощью стандартных методов HttpClientFactory можно добавлять ПО промежуточного слоя для исходящих запросов или настраивать базовый обработчик HttpClientHandler объекта HttpClient:

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var handler = new HttpClientHandler();
        handler.ClientCertificates.Add(LoadCertificate());
        return handler;
    });

Дополнительные сведения см. в статье Выполнение HTTP-запросов с помощью IHttpClientFactory.

Настройка перехватчиков

Перехватчики gRPC можно добавить к клиентам с помощью метода AddInterceptor.

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddInterceptor<LoggingInterceptor>();

Предыдущий код:

  • Регистрирует тип GreeterClient.
  • Настраивает LoggingInterceptor для этого клиента. LoggingInterceptor создается один раз и совместно используется экземплярами GreeterClient.

По умолчанию перехватчик создается один раз и совместно используется клиентами. Это поведение можно переопределить, указав область при регистрации перехватчика. Фабрику клиента можно настроить на создание нового перехватчика для каждого клиента, указав InterceptorScope.Client.

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddInterceptor<LoggingInterceptor>(InterceptorScope.Client);

Создание перехватчиков с областью действия клиента полезно в том случае, когда перехватчику требуются службы с заданной областью или временно заданной областью от внедрения зависимостей.

Для отправки Authorization метаданных с каждым запросом можно использовать перехватчик gRPC или учетные данные канала. Дополнительные сведения о настройке проверки подлинности см. в разделе Создание токена носителя с помощью фабрики клиента gRPC.

Настройка канала

Дополнительную конфигурацию можно применить к каналу с помощью метода ConfigureChannel:

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigureChannel(o =>
    {
        o.Credentials = new CustomCredentials();
    });

ConfigureChannel передается экземпляр GrpcChannelOptions. Дополнительные сведения см. в разделе Настройка параметров клиента.

Примечание.

Некоторые свойства задаются в параметре GrpcChannelOptions перед выполнением обратного вызова ConfigureChannel:

Эти значения можно переопределить.ConfigureChannel

Распространение крайнего срока и отмены

С помощью метода EnableCallContextPropagation() для клиентов gRPC, созданных производством в службе gRPC, можно настроить автоматическое распространение токена крайнего срока и отмены на дочерние вызовы. Метод расширения EnableCallContextPropagation() доступен в пакете NuGet Grpc.AspNetCore.Server.ClientFactory.

Распространение контекста вызова осуществляется путем считывания токена крайнего срока и отмены из текущего контекста запроса gRPC и его автоматического распространения на исходящие вызовы, выполняемые клиентом gRPC. Распространение контекста вызова — это отличный способ обеспечить распространение крайнего срока и отмены в случае со сложными вложенными вызовами gRPC.

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .EnableCallContextPropagation();

По умолчанию EnableCallContextPropagation вызывает ошибку, если клиент используется вне контекста вызова gRPC. Эта ошибка информирует о том, что контекст вызова для распространения отсутствует. Если вы хотите использовать клиент за пределами контекста вызова, подавите ошибку при настройке клиента с использованием SuppressContextNotFoundErrors:

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .EnableCallContextPropagation(o => o.SuppressContextNotFoundErrors = true);

Дополнительные сведения о крайних сроках и отмене RPC см. в статье Надежные службы gRPC с крайними сроками и отменой.

Именованные клиенты

Как правило, тип клиента gRPC регистрируется один раз, а затем внедряется непосредственно в конструктор типа путем внедрения зависимостей. Но есть сценарии, когда полезно иметь несколько конфигураций для одного клиента. Например, клиент, который выполняет вызовы gRPC с проверкой подлинности и без нее.

Несколько клиентов одинакового типа могут быть зарегистрированы путем присвоения каждому клиенту имени. Каждый именованный клиент может иметь собственную конфигурацию. Универсальный метод расширения AddGrpcClient имеет перегрузку, которая включает параметр имени:

services
    .AddGrpcClient<Greeter.GreeterClient>("Greeter", o =>
    {
        o.Address = new Uri("https://localhost:5001");
    });

services
    .AddGrpcClient<Greeter.GreeterClient>("GreeterAuthenticated", o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigureChannel(o =>
    {
        o.Credentials = new CustomCredentials();
    });

Предыдущий код:

  • Дважды регистрирует тип GreeterClient, указывая уникальное имя каждый раз.
  • Настраивает разные параметры для каждого именованного клиента. Регистрация GreeterAuthenticated добавляет учетные данные в канал, чтобы соответствующие вызовы gRPC прошли проверку подлинности.

Именованный клиент gRPC создается в коде приложения с помощью GrpcClientFactory. Тип и имя требуемого клиента указываются с помощью универсального метода GrpcClientFactory.CreateClient:

public class AggregatorService : Aggregator.AggregatorBase
{
    private readonly Greeter.GreeterClient _client;

    public AggregatorService(GrpcClientFactory grpcClientFactory)
    {
        _client = grpcClientFactory.CreateClient<Greeter.GreeterClient>("GreeterAuthenticated");
    }
}

Дополнительные ресурсы