分享方式:


使用 gRPC 進行錯誤處理

注意

這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。

警告

不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

如需目前的版本,請參閱 本文的 .NET 9 版本。

作者:James Newton-King

本篇文章討論錯誤處理和 gRPC:

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

內建錯誤處理

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

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

錯誤處理常用的類型如下:

  • StatusCodegRPC 狀態程式碼的列舉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);
}

上述 程式碼:

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

錯誤狀況

錯誤會以錯誤狀態程式碼和選擇性詳細資料訊息來表示 RpcExceptionRpcException 在許多狀況中會被引發:

  • 伺服器的呼叫失敗,伺服器傳了錯誤狀態程式碼。 例如,gRPC 用戶端啟動呼叫,遺漏要求訊息的必要資料,而伺服器傳回 INVALID_ARGUMENT 狀態程式碼。
  • 執行 gRPC 呼叫時,用戶端內發生錯誤。 例如,用戶端執行 gRPC 呼叫、無法連線到伺服器,並引發 UNAVAILABLE狀態的錯誤。
  • 傳遞至 gRPC 呼叫的 CancellationToken 已取消。 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.StatusProto 包含 ToRpcException 要轉換成 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}");
        }
    }
}

上述 程式碼:

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

其他資源