Nguon: Microsoft Learn · .NET 8.0

Xử lý lỗi với gRPC trên .NET

Nguồn: Error handling with gRPC on .NET

Tác giả: James Newton-King

Bài viết này thảo luận về xử lý lỗi và gRPC:

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ề:

Các loại thường được sử dụng với xử lý lỗi:

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.

cs
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:

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:

cs
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:

cs
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:

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:

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:

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:

cs
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ó:

cs
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: