Nguon: Microsoft Learn · .NET 8.0

gRPC interceptors trên .NET

Nguồn: gRPC interceptors on .NET

Tác giả: Ernest Nguyen

Interceptors (bộ chặn) là một khái niệm gRPC cho phép ứng dụng tương tác với các lời gọi gRPC đến hoặc đi. Chúng cung cấp cách làm phong phú thêm pipeline xử lý yêu cầu.

Interceptors được cấu hình cho một kênh (channel) hoặc dịch vụ và được thực thi tự động cho mỗi lời gọi gRPC. Vì interceptors trong suốt với logic ứng dụng của người dùng, chúng là giải pháp tuyệt vời cho các trường hợp phổ biến, chẳng hạn như logging (ghi log), monitoring (giám sát), authentication (xác thực) và validation (xác thực dữ liệu).

Loại Interceptor

Interceptors có thể được triển khai cho cả server gRPC và client bằng cách tạo một lớp kế thừa từ loại Interceptor:

csharp
public class ExampleInterceptor : Interceptor
{
}

Theo mặc định, lớp cơ sở Interceptor không làm gì cả. Thêm hành vi vào interceptor bằng cách ghi đè các phương thức lớp cơ sở phù hợp trong một triển khai interceptor.

Interceptors phía client

Interceptors gRPC client chặn các lời gọi RPC đi. Chúng cung cấp quyền truy cập vào yêu cầu được gửi, phản hồi đến và ngữ cảnh cho lời gọi phía client.

Các phương thức Interceptor để ghi đè cho client:

Cảnh báo: Mặc dù cả BlockingUnaryCallAsyncUnaryCall đều đề cập đến unary RPC, chúng không thể hoán đổi cho nhau. Lời gọi blocking không bị chặn bởi AsyncUnaryCall, và lời gọi bất đồng bộ không bị chặn bởi BlockingUnaryCall.

Tạo client gRPC interceptor

Đoạn code sau trình bày ví dụ cơ bản về việc chặn lời gọi bất đồng bộ của unary call:

csharp
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);
    }
}

Việc ghi đè AsyncUnaryCall:

Các phương thức trên Interceptor cho mỗi loại phương thức dịch vụ có chữ ký khác nhau. Tuy nhiên, khái niệm đằng sau các tham số continuationcontext vẫn giống nhau:

Await phản hồi trong client interceptor

Một interceptor có thể await phản hồi trong các lời gọi unary và client streaming bằng cách cập nhật giá trị AsyncUnaryCall<TResponse>.ResponseAsync hoặc AsyncClientStreamingCall<TRequest, TResponse>.ResponseAsync.

csharp
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);
        }
    }
}

Code trên:

Cấu hình client interceptors

Client interceptors gRPC được cấu hình trên một kênh.

Đoạn code sau:

csharp
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var invoker = channel.Intercept(new ClientLoggerInterceptor());

var client = new Greeter.GreeterClient(invoker);

Phương thức mở rộng Intercept có thể được nối chuỗi để cấu hình nhiều interceptors cho một kênh. Ngoài ra, có một overload Intercept chấp nhận nhiều interceptors. Có thể thực thi bất kỳ số lượng interceptors nào cho một lời gọi gRPC duy nhất, như ví dụ sau minh họa:

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

Các interceptors được gọi theo thứ tự ngược lại của các phương thức mở rộng Intercept được nối chuỗi. Trong code trên, các interceptors được gọi theo thứ tự sau:

  1. ClientLoggerInterceptor
  2. ClientMonitoringInterceptor
  3. ClientTokenInterceptor

Để biết thông tin về cách cấu hình interceptors với gRPC client factory, xem gRPC client factory integration in .NET.

Interceptors phía server

Server interceptors gRPC chặn các yêu cầu RPC đến. Chúng cung cấp quyền truy cập vào yêu cầu đến, phản hồi đi và ngữ cảnh cho lời gọi phía server.

Các phương thức Interceptor để ghi đè cho server:

Tạo server gRPC interceptor

Đoạn code sau trình bày ví dụ về việc chặn unary RPC đến:

csharp
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;
        }
    }
}

Việc ghi đè UnaryServerHandler:

Chữ ký của cả phương thức interceptors client và server đều tương tự:

Cấu hình server interceptors

Server interceptors gRPC được cấu hình khi khởi động. Đoạn code sau:

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

Một interceptor cũng có thể được cấu hình cho một dịch vụ cụ thể bằng cách sử dụng AddServiceOptions và chỉ định loại dịch vụ.

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

Các interceptors được chạy theo thứ tự chúng được thêm vào InterceptorCollection. Nếu cả interceptors toàn cục và interceptors cho dịch vụ đơn lẻ đều được cấu hình, thì các interceptors được cấu hình toàn cục chạy trước những interceptors được cấu hình cho dịch vụ đơn lẻ.

Theo mặc định, server interceptors gRPC có vòng đời theo từng yêu cầu (per-request lifetime). Có thể ghi đè hành vi này thông qua việc đăng ký loại interceptor với dependency injection. Ví dụ sau đăng ký ServerLoggerInterceptor với vòng đời singleton:

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

    services.AddSingleton<ServerLoggerInterceptor>();
}

gRPC Interceptors so với Middleware

ASP.NET Core middleware cung cấp các chức năng tương tự so với interceptors trong các ứng dụng gRPC dựa trên C-core. ASP.NET Core middleware và interceptors về mặt khái niệm tương tự nhau. Cả hai:

Sự khác biệt của gRPC Interceptor so với ASP.NET Core Middleware: