gRPC 的錯誤處理
本文討論錯誤處理和 gRPC:
- 使用 gRPC 狀態代碼和錯誤訊息的內建錯誤處理功能。
- 使用 豐富的錯誤處理傳送複雜結構化的錯誤資訊。
內建錯誤處理
gRPC 呼叫會以狀態代碼傳達成功或失敗。 gRPC 呼叫成功完成時,伺服器會將 OK
狀態傳回給用戶端。 如果發生錯誤,gRPC 會傳回:
- 錯誤狀態代碼,例如
CANCELLED
或UNAVAILABLE
。 - 選擇性字串錯誤訊息。
通常與錯誤處理搭配使用的型別如下:
StatusCode
:gRPC 狀態代碼的列舉。OK
表示成功;其他值是失敗。Status
struct
:結合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
並寫出失敗的錯誤詳細數據。
錯誤案例
錯誤會以錯誤狀態代碼和選擇性詳細資料訊息來表示 RpcException
。 RpcException
在許多案例中會擲回:
- 伺服器上的呼叫失敗,伺服器傳送錯誤狀態代碼。 例如,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.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}");
}
}
}
上述 程式碼:
- 在攔截的 try/catch
RpcException
內進行 gRPC 呼叫。 - 呼叫
GetRpcStatus()
以嘗試從例外狀況取得豐富的錯誤模型。 - 嘗試從豐富錯誤取得
BadRequest
承載的呼叫GetDetail<BadRequest>()
。
其他資源
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應