Compartir a través de


Control de errores con gRPC

Por James Newton-King

En este artículo se describe el control de errores y gRPC:

  • Funcionalidades integradas de control de errores mediante códigos de estado gRPC y mensajes de error.
  • Envío de información de error compleja y estructurada mediante control de errores enriquecido.

Control de errores integrado

Las llamadas gRPC comunican el éxito o el error con un código de estado. Cuando una llamada gRPC se completa correctamente, el servidor devuelve un estado OK al cliente. Si se produce un error, gRPC devuelve:

  • Un código de estado de error, como CANCELLED o UNAVAILABLE.
  • Un mensaje de error de cadena opcional.

Los tipos que se usan habitualmente con el control de errores son:

  • StatusCode: una enumeración de códigos de estado gRPC. OK indica que se ha realizado correctamente; los demás valores indican un error.
  • Status: un struct que combina un StatusCode y un mensaje de error de cadena opcional. El mensaje de error proporciona más detalles sobre lo que ha ocurrido.
  • RpcException: un tipo de excepción que tiene un valor Status. Esta excepción se produce en los métodos del servidor gRPC y es capturada por los clientes gRPC.

El control de errores integrado solo admite un código de estado y una descripción de cadena. Para enviar información de error compleja del servidor al cliente, use un control de errores enriquecido.

Provocar errores en el servidor

Una llamada al servidor gRPC siempre devuelve un estado. El servidor devuelve OK automáticamente cuando un método se completa correctamente.

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

El código anterior:

  • Implementa el método unario SayHello que se completa correctamente cuando devuelve un mensaje de respuesta.
  • Implementa el método de streaming SayHelloStreaming del servidor que se completa correctamente cuando finaliza el método.

Estado del error del servidor

Los métodos gRPC devuelven un código de estado de error iniciando una excepción. Cuando se produce RpcException en el servidor, el código de estado y la descripción se devuelven al cliente:

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

Los tipos de excepción que se producen y que no son de RpcException también hacen que la llamada falle, pero con un código de estado UNKNOWN y un mensaje genérico Exception was thrown by handler.

Exception was thrown by handler se envía al cliente en lugar del mensaje de excepción para evitar exponer información potencialmente confidencial. Para ver un mensaje de error más descriptivo en un entorno de desarrollo, configure EnableDetailedErrors.

Control de errores de cliente

Cuando un cliente gRPC realiza una llamada, el código de estado se valida automáticamente al acceder a la respuesta. Por ejemplo, esperar una llamada gRPC unaria devuelve el mensaje enviado por el servidor si la llamada tiene éxito, y devuelve RpcException si hay un fallo. Detectar RpcException para controlar errores en un cliente:

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

El código anterior:

  • Realiza una llamada unaria gRPC al método SayHello.
  • Escribe el mensaje de respuesta en la consola si se realiza correctamente.
  • Detecta RpcException y escribe los detalles del error en caso de error.

Escenarios de error

Los errores se representan mediante RpcException con un código de estado de error y un mensaje de detalle opcional. RpcException se produce en muchos escenarios:

  • Error de llamada en el servidor y el servidor envió un código de estado de error. Por ejemplo, el cliente gRPC inició una llamada a la que le faltaban datos necesarios del mensaje de solicitud y el servidor devuelve un código de estado de INVALID_ARGUMENT.
  • Se produjo un error dentro del cliente al realizar la llamada a gRPC. Por ejemplo, un cliente realiza una llamada a gRPC, no se puede conectar al servidor y produce un error con un estado de UNAVAILABLE.
  • El CancellationToken pasado a la llamada gRPC se cancela. La llamada gRPC se detiene y el cliente produce un error con un estado de CANCELLED.
  • Una llamada a gRPC supera la fecha límite configurada. La llamada gRPC se detiene y el cliente produce un error con un estado de DEADLINE_EXCEEDED.

Control de errores enriquecido

El control de errores enriquecido permite enviar información compleja y estructurada con mensajes de error. Por ejemplo, la validación de campos de mensaje entrantes que devuelve una lista de nombres y descripciones de campos no válidos. El modelo de error google.rpc.Status se usa a menudo para enviar información compleja sobre errores entre aplicaciones gRPC.

gRPC en .NET es compatible con un modelo de error enriquecido usando el paquete Grpc.StatusProto. Este paquete tiene métodos para crear modelos de error enriquecidos en el servidor y para que un cliente pueda leerlos. El modelo de error enriquecido se basa en las funcionalidades de control integradas de gRPC y se pueden usar en paralelo.

Importante

Los errores se incluyen en encabezados y los encabezados totales de las respuestas suelen limitarse a 8 KB (8 192 bytes). Asegúrese de que los encabezados que contienen errores no superan los 8 KB.

Creación de errores enriquecidos en el servidor

Los errores enriquecidos se crean a partir de Google.Rpc.Status. Este tipo es diferente de Grpc.Core.Status.

Google.Rpc.Status tiene campos de estado, mensaje y detalles. El campo más importante es el de detalles, que es un campo repetido de Any valores. Los detalles son donde se agregan cargas complejas.

Aunque cualquier tipo de mensaje se puede usar como carga útil, se recomienda usar una de las cargas de error estándar:

  • BadRequest
  • PreconditionFailure
  • ErrorInfo
  • ResourceInfo
  • QuotaFailure

Grpc.StatusProto incluye el método auxiliar ToRpcException para convertir Google.Rpc.Status en un error. Inicie el error del método de servidor 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();
        }
    }
}

Lectura de errores enriquecidos por un cliente

Los errores ricos se leen del RpcException capturado en el cliente. Capture la excepción y use los métodos auxiliares proporcionados por Grpc.StatusCode para obtener su instancia de 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}");
        }
    }
}

El código anterior:

  • Realiza una llamada gRPC dentro de un try/catch que detecta RpcException.
  • Llama a GetRpcStatus() a para intentar obtener el modelo de error enriquecido de la excepción.
  • Llama a GetDetail<BadRequest>() para intentar obtener una carga útil de BadRequest del error enriquecido.

Recursos adicionales