Поделиться через


Перехватчики gRPC в .NET

Автор: Эрнест Нгуен (Ernest Nguyen)

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

Перехватчики настраиваются для канала или службы и выполняются автоматически для каждого вызова gRPC. Так как перехватчики прозрачны для логики пользовательского приложения, это отличное решение для таких распространенных сценариев, как ведение журнала, мониторинг, аутентификация и проверка.

тип Interceptor

Перехватчики можно реализовать как для серверов gRPC, так и для клиентов, создав класс, наследуемый от типа Interceptor:

public class ExampleInterceptor : Interceptor
{
}

По умолчанию базовый класс Interceptor не выполняет никаких действий. Добавьте поведение в перехватчик, переопределив соответствующие методы базового класса в реализации перехватчика.

Перехватчики клиента

Перехватчики клиента gRPC перехватывают исходящие вызовы RPC. Они предоставляют доступ к отправленному запросу, входящему ответу и контексту для вызова на стороне клиента.

Методы Interceptor для переопределения для клиента:

  • BlockingUnaryCall — перехватывает блокирующий вызов унарного RPC.
  • AsyncUnaryCall — перехватывает асинхронный вызов унарного RPC.
  • AsyncClientStreamingCall — перехватывает асинхронный вызов RPC с потоковой передачей клиента.
  • AsyncServerStreamingCall — перехватывает асинхронный вызов серверного потокового RPC.
  • AsyncDuplexStreamingCall — перехватывает асинхронный вызов RPC с двунаправленной потоковой передачей.

Предупреждение

Хотя и BlockingUnaryCall, и AsyncUnaryCall относятся к унарным RPC, они не взаимозаменяемы. Блокирующий вызов не перехватывается AsyncUnaryCall, а асинхронный вызов не перехватывается BlockingUnaryCall.

Создание перехватчика gRPC клиента

Следующий код — это базовый пример перехвата асинхронного вызова унарного вызова:

public class ClientLoggingInterceptor : Interceptor
{
    private readonly ILogger _logger;

    public ClientLoggingInterceptor(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<ClientLoggingInterceptor>();
    }

    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request,
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
    {
        _logger.LogInformation("Starting call. Type/Method: {Type} / {Method}",
            context.Method.Type, context.Method.Name);
        return continuation(request, context);
    }
}

Переопределение AsyncUnaryCall:

  • Перехватывает асинхронный унарный вызов.
  • Записывает сведения о вызове.
  • Вызывает параметр continuation, переданный в метод. При этом вызывается следующий перехватчик в цепочке или базовый инициатор вызова, если это последний перехватчик.

Методы для Interceptor для каждого типа службы имеют разные сигнатуры. При этом базовая концепция, связанная с параметрами continuation и context, остается прежней:

  • continuation — это делегат, который вызывает следующий перехватчик в цепочке или базовый инициатор вызова (если в цепочке не осталось перехватчика). Не является ошибкой вызывать его ноль или несколько раз. От перехватчиков не требуется возвращать представление вызова (AsyncUnaryCall в случае унарного RPC), возвращаемое делегатом continuation. Пропуск вызова делегата и возврат собственного экземпляра представления вызова разрывает цепочку перехватчиков и немедленно возвращает связанный ответ.
  • context содержит ограниченные значения, связанные с вызовом на стороне клиента. Используйте context для передачи метаданных, таких как субъекты безопасности, учетные данные и данные трассировки. Более того, context содержит сведения о крайних сроках и отмене. Дополнительные сведения см. в статье Надежные службы gRPC с крайними сроками и отменой.

Ожидание ответа в перехватчике клиента

Перехватчик может ожидать ответа в унарных и клиентских потоковых вызовах, обновляя значение AsyncUnaryCall<TResponse>.ResponseAsync или AsyncClientStreamingCall<TRequest, TResponse>.ResponseAsync.

public class ErrorHandlerInterceptor : Interceptor
{
    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request,
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
    {
        var call = continuation(request, context);

        return new AsyncUnaryCall<TResponse>(
            HandleResponse(call.ResponseAsync),
            call.ResponseHeadersAsync,
            call.GetStatus,
            call.GetTrailers,
            call.Dispose);
    }

    private async Task<TResponse> HandleResponse<TResponse>(Task<TResponse> inner)
    {
        try
        {
            return await inner;
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException("Custom error", ex);
        }
    }
}

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

  • Создает новый перехватчик, переопределяющий AsyncUnaryCall.
  • Переопределение AsyncUnaryCall:
    • Вызывает параметр continuation для вызова следующего элемента в цепочке перехватчиков.
    • Создает новый экземпляр AsyncUnaryCall<TResponse> на основе результата продолжения.
    • Создает оболочку для задачи ResponseAsync с помощью метода HandleResponse.
    • Ожидает ответ с использованием HandleResponse. Ожидание ответа позволяет добавить логику действий после получения ответа клиентом. Ожидание ответа в блоке try-catch позволяет регистрировать ошибки из вызовов.

Дополнительные сведения о создании перехватчика клиента см в примере ClientLoggerInterceptor.cs в репозитории GitHub grpc/grpc-dotnet.

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

Перехватчики клиента gRPC настраиваются в канале.

Следующий код:

  • Создает канал с помощью GrpcChannel.ForAddress.
  • Использует метод расширения Intercept, чтобы настроить канал для использования перехватчика. Обратите внимание, что этот метод возвращает CallInvoker. Строго типизированные клиенты gRPC могут быть созданы из вызывающего объекта так же, как из канал.
  • Создает клиент из вызывающего элемента. Вызовы gRPC, выполняемые клиентом, автоматически выполняют перехватчик.
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var invoker = channel.Intercept(new ClientLoggerInterceptor());

var client = new Greeter.GreeterClient(invoker);

Метод расширения Intercept можно включить в цепочку, чтобы настроить несколько перехватчиков для канала. Кроме того, существует перегрузка Intercept, принимающая несколько перехватчиков. Для одного вызова gRPC может быть выполнено любое количество перехватчиков, как показано в следующем примере:

var invoker = channel
    .Intercept(new ClientTokenInterceptor())
    .Intercept(new ClientMonitoringInterceptor())
    .Intercept(new ClientLoggerInterceptor());

Перехватчики вызываются в порядке, обратном цепочке методов расширения Intercept. В предыдущем коде перехватчики вызываются в следующем порядке:

  1. ClientLoggerInterceptor
  2. ClientMonitoringInterceptor
  3. ClientTokenInterceptor

Дополнительные сведения о настройке перехватчиков с фабрикой клиентов gRPC см. в разделе Интеграция фабрики клиентов gRPC в .NET.

Перехватчики сервера

Перехватчики сервера gRPC перехватывают входящие запросы RPC. Они предоставляют доступ к входящему запросу, исходящему ответу и контексту для вызова на стороне сервера.

Методы Interceptor для переопределения сервера:

  • UnaryServerHandler — перехватывает унарный RPC.
  • ClientStreamingServerHandler — перехватывает клиентский поток RPC.
  • ServerStreamingServerHandler — перехватывает серверный потоковый RPC.
  • DuplexStreamingServerHandler — перехватывает RPC с двунаправленной потоковой передачей.

Создание перехватчика сервера gRPC

Следующий код — это пример перехвата входящего унарного RPC:

public class ServerLoggerInterceptor : Interceptor
{
    private readonly ILogger _logger;

    public ServerLoggerInterceptor(ILogger<ServerLoggerInterceptor> logger)
    {
        _logger = logger;
    }

    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
        TRequest request,
        ServerCallContext context,
        UnaryServerMethod<TRequest, TResponse> continuation)
    {
        _logger.LogInformation("Starting receiving call. Type/Method: {Type} / {Method}",
            MethodType.Unary, context.Method);
        try
        {
            return await continuation(request, context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error thrown by {context.Method}.");
            throw;
        }
    }
}

Переопределение UnaryServerHandler:

  • Перехватывает входящий унарный вызов.
  • Записывает сведения о вызове.
  • Вызывает параметр continuation, переданный в метод. При этом вызывается следующий перехватчик в цепочке или обработчик службы, если это последний перехватчик.
  • Записывает в журнал любые исключения. Ожидание продолжения позволяет добавить логику действий после выполнения метода службы. Ожидание продолжения в блоке try-catch позволяет регистрировать ошибки из методов.

Сигнатура методов клиентского и серверного перехватчиков аналогична:

  • continuation обозначает делегата для входящего RPC, вызывающего следующий перехватчик в цепочке или обработчик службы (если в цепочке не осталось перехватчика). Как и в случае с клиентскими перехватчиками, вы можете вызывать его в любое время. При этом нет необходимости возвращать ответ непосредственно из делегата продолжения. При ожидании продолжения можно добавить внешнюю логику действий после выполнения обработчика службы.
  • context содержит метаданные, связанные с вызовом на стороне сервера, такие как метаданные запроса, крайние сроки и отмена или результат RPC.

Дополнительные сведения о создании перехватчика сервера см в примере ServerLoggerInterceptor.cs в репозитории GitHub grpc/grpc-dotnet.

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

Перехватчики сервера gRPC настраиваются при запуске. Следующий код:

  • Добавляет gRPC в приложение с AddGrpc.
  • Настраивает ServerLoggerInterceptor для всех служб, добавляя в коллекцию Interceptors параметров службы.
public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc(options =>
    {
        options.Interceptors.Add<ServerLoggerInterceptor>();
    });
}

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

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddGrpc()
        .AddServiceOptions<GreeterService>(options =>
        {
            options.Interceptors.Add<ServerLoggerInterceptor>();
        });
}

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

По умолчанию перехватчики сервера gRPC имеют время жизни для каждого запроса. Переопределение этого поведения возможно путем регистрации типа перехватчика с внедрением зависимостей. В следующем примере регистрируется ServerLoggerInterceptor со временем жизни singleton:

public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc(options =>
    {
        options.Interceptors.Add<ServerLoggerInterceptor>();
    });

    services.AddSingleton<ServerLoggerInterceptor>();
}

Перехватчики gRPC и ПО промежуточного слоя

ПО промежуточного слоя ASP.NET Core предлагает аналогичные функциональные возможности по сравнению с перехватчиками в приложениях gRPC на основе C-Core. ПО промежуточного слоя ASP.NET Core и перехватчики выполняют одни и те же задачи. Оба варианта.

  • Используются для создания конвейера, обрабатывающего запрос gRPC.
  • Позволяют выполнять работу как до, так и после вызова следующего компонента в конвейере.
  • Предоставление доступа к HttpContext:
    • В ПО промежуточного слоя HttpContext — это параметр.
    • В перехватчиках доступ к HttpContext можно получить с помощью параметра ServerCallContext с методом расширения ServerCallContext.GetHttpContext. Эта возможность относится к перехватчикам, выполняемым в ASP.NET Core.

Отличия перехватчиков gRPC от ПО промежуточного слоя ASP.NET Core:

  • Перехватчики:
    • Работа с уровнем абстракции gRPC с помощью ServerCallContext.
    • Предоставьте доступ к:
      • Десериализованному сообщению, которое отправлено вызову.
      • Сообщению, возвращенному из вызова перед его сериализацией.
    • Могут перехватывать и обрабатывать исключения, созданные службами gRPC.
  • Промежуточного:
    • Выполняется для всех HTTP-запросов.
    • Выполняется перед перехватчиками gRPC.
    • Работает с базовыми сообщениями HTTP/2.
    • Может получать доступ только к байтам из потоков запросов и ответов.

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