使用 gRPC 进行错误处理
注意
此版本不是本文的最新版本。 有关当前版本,请参阅本文的 .NET 9 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 有关当前版本,请参阅本文的 .NET 9 版本。
本文讨论错误处理和 gRPC:
- 使用 gRPC 状态代码和错误消息的内置错误处理功能。
- 使用丰富的错误处理功能,发送复杂的结构化错误信息。
内置错误处理
gRPC 调用通过状态码来传达成功或失败的信息。 gRPC 调用成功完成后,服务器将状态返回到 OK
客户端。 如果发生错误,gRPC 将返回:
- 一个错误状态代码,例如
CANCELLED
或UNAVAILABLE
。 - 可选的字符串错误消息。
通常用于进行错误处理的类型包括:
StatusCode
:gRPC 状态代码的枚举。OK
信号成功;其他值是失败的。Status
:一个合并了StatusCode
和可选字符串错误消息的struct
。 该错误消息会提供有关所发生情况的更多详细信息。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
并写出有关失败的错误详细信息。
错误方案
错误由具有错误状态代码和可选详细信息消息的 RpcException
表示。 在很多情况下都会引发 RpcException
:
- 调用在服务器上失败,服务器发送了错误状态代码。 例如,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
有效负载。