ASP.NET Core: Trình chặn gRPC trên .NET
Trong bài viết này
Trình chặn (Interceptor) là một khái niệm gRPC cho phép các ứng dụng tương tác với các lời gọi gRPC đến hoặc đi. Trình chặn cung cấp một cách để làm phong phú thêm quy trình xử lý request.
Trình chặn được định cấu hình cho một kênh hoặc dịch vụ và được thực thi tự động cho mỗi lệnh gọi gRPC. Vì các trình chặn có tính minh bạch đối với logic ứng dụng của người dùng nên chúng là giải pháp tuyệt vời cho các trường hợp thông thường, chẳng hạn như ghi nhật ký, giám sát, xác thực (authentication) và xác nhận (validation).
Kiểu trình chặn
Trình chặn có thể được triển khai cho cả máy chủ và máy khách gRPC bằng cách tạo một lớp kế thừa từ kiểu 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 cho trình chặn bằng cách ghi đè các phương thức lớp cơ sở thích hợp trong quá trình triển khai trinh chặn.
Trình chặn máy khách
Trình chặn máy khách gRPC chặn các lệnh gọi RPC gửi đi. Chúng cung cấp quyền truy cập vào request đã gửi, phản hồi đến và ngữ cảnh cho lời gọi phía máy khách.
Các phương thức của Interceptor
ghi đè cho trình khách:
BlockingUnaryCall
: Chặn lệnh gọi chặn của RPC đơn nhất.AsyncUnaryCall
: Chặn lệnh gọi không đồng bộ của RPC đơn nhất.AsyncClientStreamingCall
: Chặn lệnh gọi không đồng bộ của RPC truyền phát máy khách.AsyncServerStreamingCall
: Chặn lệnh gọi không đồng bộ của RPC truyền phát máy chủ.AsyncDuplexStreamingCall
: Chặn lệnh gọi không đồng bộ của RPC truyền phát hai chiều.
Cảnh báo
Mặc dù cả
BlockingUnaryCall
vàAsyncUnaryCall
đều đề cập đến các RPC đơn nhất nhưng chúng không thể thay thế cho nhau. Vì thế mà lệnh gọi chặn không bị chặn bởiAsyncUnaryCall
, và lệnh gọi không đồng bộ không bị chặn bởiBlockingUnaryCall
.
Tạo trình chặn chặn gRPC của máy khách
Đoạn mã sau đây trình bày một ví dụ cơ bản về việc chặn lệnh gọi không đồng bộ của lời gọi đơn nhất:
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);
}
}
Ghi đè AsyncUnaryCall
:
- Chặn lời gọi đơn nhất không đồng bộ.
- Nhật ký 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 trình chặn tiếp theo trong kết chuỗi hoặc trình gọi lệnh gọi cơ bản nếu đây là trình chặn cuối cùng.
Các phương thức trên Interceptor
đối với từng loại phương thức dịch vụ có signature khác nhau. Tuy nhiên, khái niệm đằng sau các tham số continuation
và context
vẫn giữ nguyên:
continuation
là một delegate gọi trình chặn tiếp theo trong kết chuỗi (chain) hoặc trình gọi lời gọi cơ bản (nếu không còn trình chặn nào trong kết chuỗi). Sẽ không có lỗi nếu gọi nó là không hoặc nhiều lần. Trình chặn không bắt buộc phải trả về biểu diễn lời gọi (AsyncUnaryCall
trong trường hợp RPC đơn nhất) được trả về từ delegatecontinuation
. Việc bỏ qua lời gọi delegate và trả về thể hiện đại diện lời gọi của riêng bạn sẽ phá vỡ kết chuỗi của trình chặn và trả về phản hồi liên quan ngay lập tức.context
mang các giá trị trong phạm vi được liên kết với lời gọi phía máy khách. Sử dụngcontext
để truyền siêu dữ liệu, chẳng hạn như nguyên tắc bảo mật, thông tin xác thực hoặc dữ liệu theo dõi. Hơn nữa,context
mang thông tin về thời hạn và hủy bỏ. Để biết thêm thông tin, hãy xem Dịch vụ gRPC đáng tin cậy có thời hạn và hủy.
Chờ phản hồi trong trình chặn của máy khách
Trình chặn có thể chờ phản hồi trong các lời gọi truyền phát đơn nhất và truyền phát máy khách 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);
}
}
}
Đoạn code trên:
- Tạo một trình chặn mới ghi đè
AsyncUnaryCall
. - Ghi đè
AsyncUnaryCall
:- Gọi tham số
continuation
để gọi mục tiếp theo trong kết chuỗi trình chặn. - Tạo một thể hiện
AsyncUnaryCall<TResponse>
mới dựa trên kết quả của việc tiếp tục. - Kết thúc tác vụ
ResponseAsync
bằng cách sử dụng phương thứcHandleResponse
. - Chờ phản hồi với
HandleResponse
. Việc chờ phản hồi cho phép thêm logic sau khi máy khách nhận được phản hồi. Bằng cách chờ phản hồi trong khối try-catch, lỗi từ các lời gọi có thể được ghi lại.
- Gọi tham số
Để biết thêm thông tin về cách tạo trình chặn chặn máy khách, hãy xem Ví dụ ClientLoggerInterceptor.cs trong kho lưu trữ Github grpc/grpc-dotnet.
Định cấu hình chặn máy khách
Trình chặn máy khách gRPC được định cấu hình trên một kênh.
Đoạn code sau sẽ:
- Tạo một kênh bằng cách sử dụng
GrpcChannel.ForAddress
. - Sử dụng phương thức mở rộng
Intercept
để định cấu hình kênh để sử dụng trình chặn. Lưu ý rằng phương thức này trả về mộtCallInvoker
. Các máy khách gRPC được định kiểu mạnh có thể được tạo từ một trình kích hoạt giống như một kênh. - Tạo một ứng dụng khách từ trình gọi. Các lời gọi gRPC do máy khách thực hiện sẽ tự động thực thi trình chặn.
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 kết chuỗi để định cấu hình nhiều trình chặn cho một kênh. Ngoài ra, có phương thức quá tải (overloading) Intercept
chấp nhận nhiều trình chặn. Bất kỳ số lượng trình chặn nào cũng có thể được thực thi cho một lời gọi gRPC, như ví dụ sau minh họa:
var invoker = channel
.Intercept(new ClientTokenInterceptor())
.Intercept(new ClientMonitoringInterceptor())
.Intercept(new ClientLoggerInterceptor());
Các trình chặn được gọi theo thứ tự ngược lại của các phương thức mở rộng Intercept
đã được kết chuỗi. Trong đoạn code trên, các trình chặn được gọi theo thứ tự sau:
ClientLoggerInterceptor
ClientMonitoringInterceptor
ClientTokenInterceptor
Để biết thông tin về cách định cấu hình thiết bị chặn với factory máy khách gRPC, hãy xem tích hợp factory máy khách gRPC trong .NET.
Trình chặn máy chủ
Trình chặn máy chủ 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 gửi đi và bối cảnh cho lời gọi phía máy chủ.
Các phương thức Interceptor
để ghi đè cho máy chủ:
UnaryServerHandler
: Chặn một RPC đơn nhất.ClientStreamingServerHandler
: Chặn RPC trình phát máy khách.ServerStreamingServerHandler
: Chặn RPC trình phát máy chủ.DuplexStreamingServerHandler
: Chặn RPC trình phát hai chiều.
Tạo trình chặn máy chủ gRPC
Đoạn mã sau trình bày một ví dụ về việc chặn RPC đơn nhất đế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;
}
}
}
Ghi đè UnaryServerHandler
:
- Chặn một lời gọi đơn nhất đến.
- Nhật ký chi tiết về lời gọi.
- Lời gọi tham số
continuation
được truyền vào phương thức. Điều này gọi trình chặn tiếp theo trong kết chuỗi hoặc trình xử lý dịch vụ nếu đây là trình chặn cuối cùng. - Ghi lại mọi trường hợp ngoại lệ. Việc chờ tiếp tục cho phép thêm logic sau khi phương thức dịch vụ được thực thi. Bằng cách chờ tiếp tục trong khối try-catch, lỗi từ các phương thức có thể được ghi lại.
Chữ ký (Signature) của cả hai phương thức chặn máy khách và máy chủ đều giống nhau:
continuation
là viết tắt của delegate cho một RPC đến đang gọi thiết bị chặn tiếp theo trong kết chuỗi hoặc trình xử lý dịch vụ (nếu không còn thiết bị chặn nào trong kết chuỗi). Tương tự như các trình chặn máy khách, bạn có thể gọi nó bất cứ lúc nào và không cần phải trả lại phản hồi trực tiếp từ delegate tiếp tục. Logic gửi đi có thể được thêm vào sau khi trình xử lý dịch vụ đã thực thi bằng cách chờ tiếp tục.context
mang siêu dữ liệu được liên kết với lời gọi phía máy chủ, chẳng hạn như siêu dữ liệu yêu cầu, thời hạn và hủy hoặc kết quả RPC.
Để biết thêm thông tin về cách tạo trình chặn máy chủ, hãy xem Ví dụ ServerLoggerInterceptor.cs trong kho GitHub grpc/grpc-dotnet.
Định cấu hình trình chặn máy chủ
Trình chặn máy chủ gRPC được định cấu hình khi khởi động. Đoạn code sau sẽ:
- Thêm gRPC vào ứng dụng với
AddGrpc
. - Định cấu hình
ServerLoggerInterceptor
cho tất cả các dịch vụ bằng cách thêm nó vào collectionInterceptors
của tùy chọn dịch vụ.
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc(options =>
{
options.Interceptors.Add<ServerLoggerInterceptor>();
});
}
Trình chặn 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 trình chặn được chạy theo thứ tự chúng được thêm vào file InterceptorCollection
. Nếu cả hai trình chặn dịch vụ đơn lẻ và toàn cầu đều được định cấu hình thì các trình chặn được định cấu hình toàn cầu sẽ chạy trước các trình chặn được định cấu hình cho một dịch vụ.
Theo mặc định, các trình chặn máy chủ gRPC có thời gian tồn tại theo yêu cầu. Có thể ghi đè hành vi này thông qua việc đăng ký loại trình chặn với chèn phụ thuộc (DI). Ví dụ sau đăng ký ServerLoggerInterceptor
với vòng đời đơn (singleton lifetime):
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc(options =>
{
options.Interceptors.Add<ServerLoggerInterceptor>();
});
services.AddSingleton<ServerLoggerInterceptor>();
}
Trình chặn gRPC so với Middleware
Middleware ASP.NET Core cung cấp các chức năng tương tự so với các trình chặn trong ứng dụng gRPC dựa trên lõi C. Middleware và trình chặn của ASP.NET Core giống nhau về mặt khái niệm. Cả hai:
- Được sử dụng để xây dựng một quy trình xử lý yêu cầu gRPC.
- Cho phép công việc được thực hiện trước hoặc sau thành phần tiếp theo trong quy trình.
- Cung cấp quyền truy cập vào
HttpContext
:- Trong middleware,
HttpContext
là một tham số. - Trong trình chặn,
HttpContext
có thể được truy cập bằng cách sử dụng tham sốServerCallContext
với phương thức mở rộngServerCallContext.GetHttpContext
. Tính năng này dành riêng cho các trình chặn chạy trong ASP.NET Core.
- Trong middleware,
Sự khác biệt giữa Trình chặn gRPC Interceptor và Middleware ASP.NET Core:
- Trình chặn:
- Vận hành trên lớp trừu tượng gRPC bằng cách sử dụng ServerCallContext.
- Cung cấp quyền truy cập vào:
- Tin nhắn (Message) đã được giải tuần tự hóa được gửi đến một lời gọi.
- Tin nhắn được trả về từ lời gọi trước khi nó được đăng theo thứ tự.
- Có thể bắt và xử lý các ngoại lệ được gửi 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 các trình chặn gRPC.
- Hoạt động trên các thông báo HTTP/2 cơ bản.
- Chỉ có thể truy cập byte từ luồng yêu cầu (request) và phản hồi (response).
Tài nguyên bổ sung
- Tổng quan về gRPC trên .NET
- Tạo các dịch vụ và phương thức gRPC
- Gọi các dịch vụ gRPC bằng máy khách .NET
- Ví dụ về cách sử dụng gRPC trên máy khách và máy chủ ( grpc/grpc-dotnetkho GitHub)
- Định cấu hình trình chặn trong factory máy khách gRPC trong .NET