Xử lý lỗi với gRPC trên .NET
Tác giả: James Newton-King
Bài viết này thảo luận về xử lý lỗi và gRPC:
- Khả năng xử lý lỗi tích hợp sẵn sử dụng status codes (mã trạng thái) và thông báo lỗi gRPC.
- Gửi thông tin lỗi phức tạp, có cấu trúc bằng cách sử dụng rich error handling (xử lý lỗi phong phú).
Xử lý lỗi tích hợp sẵn
Các lời gọi gRPC giao tiếp thành công hay thất bại bằng status code. Khi lời gọi gRPC hoàn thành thành công, server trả về trạng thái OK cho client. Nếu xảy ra lỗi, gRPC trả về:
- Một mã trạng thái lỗi, chẳng hạn như
CANCELLEDhoặcUNAVAILABLE. - Một thông báo lỗi string tùy chọn.
Các loại thường được sử dụng với xử lý lỗi:
StatusCode: Một enumeration (kiểu liệt kê) của gRPC status codes.OKbáo hiệu thành công; các giá trị khác là thất bại.Status: Mộtstructkết hợpStatusCodevà thông báo lỗi string tùy chọn. Thông báo lỗi cung cấp thêm chi tiết về những gì đã xảy ra.RpcException: Một loại ngoại lệ có giá trịStatus. Ngoại lệ này được ném ra trong các phương thức server gRPC và được bắt bởi các gRPC client.
Xử lý lỗi tích hợp sẵn chỉ hỗ trợ status code và mô tả string. Để gửi thông tin lỗi phức tạp từ server đến client, hãy sử dụng rich error handling.
Ném lỗi phía server
Lời gọi server gRPC luôn trả về một trạng thái. Server tự động trả về OK khi một phương thức hoàn thành thành công.
public class GreeterService : GreeterBase
{
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}" });
}
public override async Task SayHelloStreaming(HelloRequest request,
IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
{
for (var i = 0; i < 5; i++)
{
await responseStream.WriteAsync(new HelloReply { Message = $"Hello {request.Name} {i}" });
await Task.Delay(TimeSpan.FromSeconds(1));
}
}
}Code trên:
- Triển khai phương thức unary
SayHellohoàn thành thành công khi trả về thông điệp phản hồi. - Triển khai phương thức server streaming
SayHelloStreaminghoàn thành thành công khi phương thức kết thúc.
Mã trạng thái lỗi server
Các phương thức gRPC trả về mã trạng thái lỗi bằng cách ném ra một ngoại lệ. Khi RpcException được ném ra trên server, mã trạng thái và mô tả của nó được trả về cho client:
public class GreeterService : GreeterBase
{
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
if (string.IsNullOrEmpty(request.Name))
{
throw new RpcException(new Status(StatusCode.InvalidArgument, "Name is required."));
}
return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}" });
}
}Các loại ngoại lệ bị ném ra không phải là RpcException cũng làm cho lời gọi thất bại, nhưng với mã trạng thái UNKNOWN và thông điệp chung Exception was thrown by handler.
Exception was thrown by handler được gửi đến client thay vì thông điệp ngoại lệ để ngăn lộ thông tin nhạy cảm. Để xem thông báo lỗi mô tả hơn trong môi trường development, hãy cấu hình EnableDetailedErrors.
Xử lý lỗi phía client
Khi gRPC client thực hiện lời gọi, status code được tự động xác thực khi truy cập phản hồi. Ví dụ, await một lời gọi gRPC unary trả về thông điệp được gửi bởi server nếu lời gọi thành công, và ném ra RpcException nếu có lỗi. Bắt RpcException để xử lý lỗi trong một client:
var client = new Greet.GreeterClient(channel);
try
{
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });
Console.WriteLine("Greeting: " + response.Message);
}
catch (RpcException ex)
{
Console.WriteLine("Status code: " + ex.Status.StatusCode);
Console.WriteLine("Message: " + ex.Status.Detail);
}Code trên:
- Thực hiện lời gọi gRPC unary đến phương thức
SayHello. - Ghi thông điệp phản hồi ra console nếu thành công.
- Bắt
RpcExceptionvà ghi ra chi tiết lỗi khi thất bại.
Các tình huống lỗi
Lỗi được biểu thị bằng RpcException với mã trạng thái lỗi và thông điệp chi tiết tùy chọn. RpcException được ném ra trong nhiều tình huống:
- Lời gọi thất bại trên server và server gửi mã trạng thái lỗi. Ví dụ, gRPC client bắt đầu lời gọi thiếu dữ liệu bắt buộc từ thông điệp yêu cầu và server trả về mã trạng thái
INVALID_ARGUMENT. - Xảy ra lỗi bên trong client khi thực hiện lời gọi gRPC. Ví dụ, client thực hiện lời gọi gRPC, không thể kết nối đến server và ném ra lỗi với trạng thái
UNAVAILABLE. CancellationTokenđược truyền vào lời gọi gRPC bị hủy. Lời gọi gRPC bị dừng và client ném ra lỗi với trạng tháiCANCELLED.- Lời gọi gRPC vượt quá deadline (thời hạn) đã cấu hình. Lời gọi gRPC bị dừng và client ném ra lỗi với trạng thái
DEADLINE_EXCEEDED.
Rich error handling (Xử lý lỗi phong phú)
Rich error handling cho phép gửi thông tin phức tạp, có cấu trúc cùng với thông điệp lỗi. Ví dụ, xác thực các trường thông điệp đến trả về danh sách tên trường và mô tả không hợp lệ. Mô hình lỗi google.rpc.Status thường được sử dụng để gửi thông tin lỗi phức tạp giữa các ứng dụng gRPC.
gRPC trên .NET hỗ trợ mô hình lỗi phong phú bằng cách sử dụng gói Grpc.StatusProto. Gói này có các phương thức để tạo rich error models trên server và đọc chúng bởi client. Mô hình lỗi phong phú xây dựng trên các khả năng xử lý tích hợp sẵn của gRPC và chúng có thể được sử dụng song song.
Quan trọng: Lỗi được bao gồm trong headers, và tổng số headers trong các phản hồi thường bị giới hạn ở 8 KB (8.192 byte). Đảm bảo rằng headers chứa lỗi không vượt quá 8 KB.
Tạo rich errors trên server
Rich errors được tạo từ Google.Rpc.Status. Loại này khác với Grpc.Core.Status.
Google.Rpc.Status có các trường status, message và details. Trường quan trọng nhất là details, là một trường lặp lại của các giá trị Any. Details là nơi thêm các payload phức tạp.
Mặc dù bất kỳ loại thông điệp nào cũng có thể được sử dụng làm payload, nhưng khuyến nghị sử dụng một trong các payload lỗi tiêu chuẩn:
BadRequestPreconditionFailureErrorInfoResourceInfoQuotaFailure
Grpc.StatusProto bao gồm ToRpcException là phương thức helper để chuyển đổi Google.Rpc.Status thành lỗi. Ném lỗi từ phương thức server gRPC:
public class GreeterService : Greeter.GreeterBase
{
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
ArgumentNotNullOrEmpty(request.Name);
return Task.FromResult(new HelloReply { Message = "Hello " + request.Name });
}
public static void ArgumentNotNullOrEmpty(string value, [CallerArgumentExpression(nameof(value))] string? paramName = null)
{
if (string.IsNullOrEmpty(value))
{
var status = new Google.Rpc.Status
{
Code = (int)Code.InvalidArgument,
Message = "Bad request",
Details =
{
Any.Pack(new BadRequest
{
FieldViolations =
{
new BadRequest.Types.FieldViolation { Field = paramName, Description = "Value is null or empty" }
}
})
}
};
throw status.ToRpcException();
}
}
}Đọc rich errors bởi client
Rich errors được đọc từ RpcException bị bắt trong client. Bắt ngoại lệ và sử dụng các phương thức helper được cung cấp bởi Grpc.StatusCode để lấy instance Google.Rpc.Status của nó:
var client = new Greet.GreeterClient(channel);
try
{
var reply = await client.SayHelloAsync(new HelloRequest { Name = name });
Console.WriteLine("Greeting: " + reply.Message);
}
catch (RpcException ex)
{
Console.WriteLine($"Server error: {ex.Status.Detail}");
var badRequest = ex.GetRpcStatus()?.GetDetail<BadRequest>();
if (badRequest != null)
{
foreach (var fieldViolation in badRequest.FieldViolations)
{
Console.WriteLine($"Field: {fieldViolation.Field}");
Console.WriteLine($"Description: {fieldViolation.Description}");
}
}
}Code trên:
- Thực hiện lời gọi gRPC bên trong try/catch bắt
RpcException. - Gọi
GetRpcStatus()để cố gắng lấy mô hình lỗi phong phú từ ngoại lệ. - Gọi
GetDetail<BadRequest>()để cố gắng lấy payloadBadRequesttừ lỗi phong phú.