共用方式為


gRPC 的錯誤處理

作者:James Newton-King

本文討論錯誤處理和 gRPC:

  • 使用 gRPC 狀態代碼和錯誤訊息的內建錯誤處理功能。
  • 使用 豐富的錯誤處理傳送複雜結構化的錯誤資訊。

內建錯誤處理

gRPC 呼叫會以狀態代碼傳達成功或失敗。 gRPC 呼叫成功完成時,伺服器會將 OK 狀態傳回給用戶端。 如果發生錯誤,gRPC 會傳回:

  • 錯誤狀態代碼,例如 CANCELLEDUNAVAILABLE
  • 選擇性字串錯誤訊息。

通常與錯誤處理搭配使用的型別如下:

  • StatusCode:gRPC 狀態代碼列舉。 OK 表示成功;其他值是失敗。
  • Statusstruct:結合 StatusCode 和選擇性字串錯誤訊息的 。 錯誤訊息提供有關所發生狀況的進一步詳細數據。
  • RpcException:具有 Status 值的例外狀況類型。 此例外狀況會在 gRPC 伺服器方法中擲回,並由 gRPC 客戶端攔截。

內建錯誤處理僅支援狀態代碼和字串描述。 若要將複雜的錯誤資訊從伺服器傳送到用戶端, 請使用豐富的錯誤處理

擲回伺服器錯誤

gRPC 伺服器呼叫一律會傳回狀態。 當方法成功完成時,伺服器會自動傳回 OK

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

上述 程式碼:

  • 實作一元 SayHello 方法,這個方法會在傳回回應消息時順利完成。
  • 實作在方法完成時成功完成的伺服器串流 SayHelloStreaming 方法。

伺服器錯誤狀態

gRPC 方法會擲回例外狀況來傳回錯誤狀態代碼。 RpcException在伺服器上擲回 時,其狀態代碼和描述會傳回給用戶端:

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

擲回的例外狀況類型,不是 RpcException 也會導致呼叫失敗,但具有 UNKNOWN 狀態代碼和泛型訊息 Exception was thrown by handler

Exception was thrown by handler 會傳送至用戶端,而不是例外狀況訊息,以防止公開潛在的敏感性資訊。 若要在開發環境中查看更具描述性的錯誤訊息,請設定 EnableDetailedErrors

處理客戶端錯誤

當 gRPC 用戶端進行呼叫時,狀態代碼會在存取回應時自動驗證。 例如,等候一元 gRPC 呼叫會傳回伺服器在呼叫成功時所傳送的訊息,並在失敗時擲回 RpcException 。 攔截 RpcException 以處理用戶端中的錯誤:

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

上述 程式碼:

  • 對方法進行一元 gRPC 呼叫 SayHello
  • 如果成功,請將回應消息寫入主控台。
  • 擷取 RpcException 並寫出失敗的錯誤詳細數據。

錯誤案例

錯誤會以錯誤狀態代碼和選擇性詳細資料訊息來表示 RpcExceptionRpcException 在許多案例中會擲回:

  • 伺服器上的呼叫失敗,伺服器傳送錯誤狀態代碼。 例如,gRPC 用戶端啟動呼叫,該呼叫遺漏要求訊息的必要數據,而伺服器會傳 INVALID_ARGUMENT 回狀態代碼。
  • 進行 gRPC 呼叫時,用戶端內發生錯誤。 例如,客戶端進行 gRPC 呼叫、無法連線到伺服器,並擲回狀態 UNAVAILABLE為的錯誤。
  • CancellationToken傳遞至 gRPC 呼叫的 已取消。 gRPC 呼叫已停止,且客戶端擲回狀態為 CANCELLED的錯誤。
  • gRPC 通話超過其設定的期限。 gRPC 呼叫已停止,且客戶端擲回狀態為 DEADLINE_EXCEEDED的錯誤。

豐富的錯誤處理

豐富的錯誤處理可讓具有錯誤訊息的複雜結構化資訊傳送。 例如,驗證傳回無效功能變數名稱和描述清單的傳入訊息欄位。 google.rpc.Status錯誤模型通常用來在 gRPC 應用程式之間傳送複雜的錯誤資訊。

.NET 上的 gRPC 支援使用 Grpc.StatusProto 套件的豐富錯誤模型。 此套件具有在伺服器上建立豐富錯誤模型的方法,並由用戶端讀取它們。 豐富的錯誤模型建置在 gRPC 的內建處理功能之上,而且可以並存使用。

重要

錯誤會包含在標頭中,回應中的標頭總數通常限製為8 KB(8,192個字節)。 請確定包含錯誤的標頭不超過 8 KB。

在伺服器上建立豐富錯誤

Google.Rpc.Status建立豐富的錯誤。 此類型Grpc.Core.Status不同

Google.Rpc.Status 具有狀態、訊息和詳細數據欄位。 最重要的欄位是詳細數據,這是重複的值 Any 欄位。 詳細數據是新增複雜承載的位置。

雖然任何訊息類型都可以作為承載使用,但建議使用其中 一個標準錯誤承載

  • BadRequest
  • PreconditionFailure
  • ErrorInfo
  • ResourceInfo
  • QuotaFailure

Grpc.StatusProtoToRpcException包含要轉換成Google.Rpc.Status錯誤的協助程式方法。 從 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();
        }
    }
}

讀取客戶端的豐富錯誤

從客戶端中攔截到的 讀取 RpcException 豐富錯誤。 攔截例外狀況,並使用 所提供的 Grpc.StatusCode 協助程式方法來取得其 Google.Rpc.Status 實例:

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

上述 程式碼:

  • 在攔截的 try/catch RpcException內進行 gRPC 呼叫。
  • 呼叫 GetRpcStatus() 以嘗試從例外狀況取得豐富的錯誤模型。
  • 嘗試從豐富錯誤取得BadRequest承載的呼叫GetDetail<BadRequest>()

其他資源