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:
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:
BlockingUnaryCall: Chặn lời gọi blocking (đồng bộ) của unary RPC.AsyncUnaryCall: Chặn lời gọi bất đồng bộ của unary RPC.AsyncClientStreamingCall: Chặn lời gọi bất đồng bộ của client-streaming RPC.AsyncServerStreamingCall: Chặn lời gọi bất đồng bộ của server-streaming RPC.AsyncDuplexStreamingCall: Chặn lời gọi bất đồng bộ của bidirectional-streaming RPC (RPC truyền luồng hai chiều).
Cảnh báo: Mặc dù cả BlockingUnaryCall và AsyncUnaryCall đề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:
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:
- Chặn lời gọi unary bất đồng bộ.
- Ghi log chi tiết về lời gọi.
- Gọi tham số
continuationđược truyền vào phương thức. Điều này gọi interceptor tiếp theo trong chuỗi hoặc call invoker bên dưới nếu đây là interceptor cuối cùng.
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ố continuation và context vẫn giống nhau:
continuationlà một delegate gọi interceptor tiếp theo trong chuỗi hoặc call invoker bên dưới (nếu không còn interceptor nào trong chuỗi). Không phải là lỗi khi gọi nó không lần nào hoặc nhiều lần. Interceptors không bắt buộc phải trả về biểu diễn lời gọi được trả về từ delegatecontinuation. Bỏ qua lời gọi delegate và trả về instance của biểu diễn lời gọi của riêng bạn sẽ phá vỡ chuỗi interceptors và trả về phản hồi liên quan ngay lập tức.contextmang các giá trị scoped liên quan đến lời gọi phía client. Sử dụngcontextđể truyền metadata, chẳng hạn như security principals (danh tính bảo mật), credentials (thông tin xác thực), hoặc tracing data (dữ liệu theo dõi). Hơn nữa,contextmang thông tin về deadlines và cancellation.
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.
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:
- Tạo một interceptor mới ghi đè
AsyncUnaryCall. - Việc ghi đè
AsyncUnaryCall: - Gọi tham số
continuationđể gọi phần tử tiếp theo trong chuỗi interceptor. - Tạo một instance
AsyncUnaryCall<TResponse>mới dựa trên kết quả của continuation. - Bọc task
ResponseAsyncbằng phương thứcHandleResponse. - Await phản hồi với
HandleResponse. Việc await phản hồi cho phép thêm logic sau khi client nhận được phản hồi. Bằng cách await phản hồi trong một khối try-catch, các lỗi từ lời gọi có thể được ghi log.
Cấu hình client interceptors
Client interceptors gRPC được cấu hình trên một kênh.
Đoạn code sau:
- Tạo một kênh bằng
GrpcChannel.ForAddress. - Sử dụng phương thức mở rộng
Interceptđể cấu hình kênh sử dụng interceptor. Lưu ý rằng phương thức này trả về mộtCallInvoker. Các gRPC client được định kiểu mạnh có thể được tạo từ một invoker giống như một kênh. - Tạo một client từ invoker. Các lời gọi gRPC được thực hiện bởi client tự động thực thi interceptor.
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:
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:
ClientLoggerInterceptorClientMonitoringInterceptorClientTokenInterceptor
Để 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:
UnaryServerHandler: Chặn unary RPC.ClientStreamingServerHandler: Chặn client-streaming RPC.ServerStreamingServerHandler: Chặn server-streaming RPC.DuplexStreamingServerHandler: Chặn bidirectional-streaming RPC.
Tạo server gRPC interceptor
Đoạn code sau trình bày ví dụ về việc chặn unary RPC đến:
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ặn lời gọi unary đến.
- Ghi log chi tiết về lời gọi.
- Gọi tham số
continuationđược truyền vào phương thức. Điều này gọi interceptor tiếp theo trong chuỗi hoặc service handler nếu đây là interceptor cuối cùng. - Ghi log các ngoại lệ. Việc await continuation cho phép thêm logic sau khi phương thức dịch vụ đã thực thi. Bằng cách await continuation trong một khối try-catch, các lỗi từ phương thức có thể được ghi log.
Chữ ký của cả phương thức interceptors client và server đều tương tự:
continuationđại diện cho một delegate cho một RPC đến gọi interceptor tiếp theo trong chuỗi hoặc service handler (nếu không còn interceptor nào trong chuỗi). Tương tự như client interceptors, bạn có thể gọi nó bất kỳ lúc nào và không cần phải trả về phản hồi trực tiếp từ delegate continuation. Logic outbound có thể được thêm sau khi service handler đã thực thi bằng cách await continuation.contextmang metadata liên quan đến lời gọi phía server, chẳng hạn như request metadata, deadlines và cancellation, hoặc kết quả RPC.
Cấu hình server interceptors
Server interceptors gRPC được cấu hình khi khởi động. Đoạn code sau:
- Thêm gRPC vào ứng dụng với
AddGrpc. - Cấu hình
ServerLoggerInterceptorcho tất cả dịch vụ bằng cách thêm nó vào tập hợpInterceptorscủa tùy chọn dịch vụ.
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ụ.
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:
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:
- Được dùng để xây dựng pipeline xử lý yêu cầu gRPC.
- Cho phép thực hiện công việc trước hoặc sau component tiếp theo trong pipeline.
- Cung cấp quyền truy cập vào
HttpContext: - Trong middleware,
HttpContextlà một tham số. - Trong interceptors,
HttpContextcó thể được truy cập bằng tham sốServerCallContextvới phương thức mở rộngServerCallContext.GetHttpContext. Tính năng này dành riêng cho các interceptors chạy trong ASP.NET Core.
Sự khác biệt của gRPC Interceptor so với ASP.NET Core Middleware:
- Interceptors:
- Hoạt động trên tầng trừu tượng (layer of abstraction) gRPC bằng cách sử dụng
ServerCallContext. - Cung cấp quyền truy cập vào:
- Thông điệp đã được deserialized gửi đến lời gọi.
- Thông điệp được trả về từ lời gọi trước khi nó được serialized.
- Có thể bắt và xử lý các ngoại lệ được ném ra từ các dịch vụ gRPC.
- Middleware:
- Chạy cho tất cả các yêu cầu HTTP.
- Chạy trước gRPC interceptors.
- Hoạt động trên các thông điệp HTTP/2 bên dưới.
- Chỉ có thể truy cập bytes từ các luồng yêu cầu và phản hồi.