Compartilhar via


Tratamento de erro com o gRPC

Por James Newton-King

Este artigo descreve o tratamento de erro e o gRPC:

  • Recursos de tratamento de erro interno por meio de códigos de status e mensagens de erro do gRPC.
  • Envio de informações de erro complexas e estruturadas usando o tratamento avançado de erro.

Tratamento de erro interno

As chamadas gRPC comunicam o sucesso ou a falha com um código de status. Quando uma chamada gRPC é concluída com êxito, o servidor retorna um status OK para o cliente. Se ocorrer um erro, o gRPC retornará:

  • Um código de status de erro, como CANCELLED ou UNAVAILABLE.
  • Uma mensagem de erro de cadeia de caracteres opcional.

Os tipos comumente usados com o tratamento de erro são:

  • StatusCode: uma enumeração de códigos de status gRPC. OK sinaliza o sucesso; outros valores indicam uma falha.
  • Status: um struct que combina um StatusCode e uma mensagem de erro de cadeia de caracteres opcional. A mensagem de erro fornece mais detalhes sobre o que aconteceu.
  • RpcException: um tipo de exceção que tem o valor Status. Essa exceção é gerada nos métodos de servidor gRPC e capturada por clientes gRPC.

O tratamento de erro interno só dá suporte a um código de status e uma descrição de cadeia de caracteres. Para enviar informações de erro complexas do servidor para o cliente, use o tratamento avançado de erro.

Gerar erros do servidor

Uma chamada de servidor gRPC sempre retorna um status. O servidor retorna OK automaticamente quando um método é concluído com êxito.

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

O código anterior:

  • Implementa o método unário SayHello que é concluído com êxito quando retorna uma mensagem de resposta.
  • Implementa o método SayHelloStreaming de streaming do servidor que é concluído com êxito quando o método é concluído.

Status de erro do servidor

Os métodos gRPC retornam um código de status de erro gerando uma exceção. Quando uma RpcException é gerada no servidor, o código de status e a descrição são retornados ao 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}" });
    }
}

Os tipos de exceção gerados que não são RpcException também causam falha na chamada, mas com um código de status UNKNOWN e uma mensagem genérica Exception was thrown by handler.

Exception was thrown by handler é enviada ao cliente em vez da mensagem de exceção, a fim de impedir a exposição de informações potencialmente confidenciais. Para ver uma mensagem de erro mais descritiva em um ambiente de desenvolvimento, configure EnableDetailedErrors.

Tratar erros do cliente

Quando um cliente gRPC faz uma chamada, o código de status é validado automaticamente quando a resposta é acessada. Por exemplo, aguardar uma chamada gRPC unária retorna a mensagem enviada pelo servidor se a chamada é bem-sucedida e gera uma, em caso de uma falha RpcException. Captura RpcException para tratar erros em um 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);
}

O código anterior:

  • Faz uma chamada gRPC unária ao método SayHello.
  • Grava a mensagem de resposta no console caso ela seja bem-sucedida.
  • Captura RpcException e grava os detalhes do erro em caso de falha.

Cenários de erro

Os erros são representados por RpcException com um código de status de erro e uma mensagem de detalhes opcional. RpcException é gerado em muitos cenários:

  • A chamada falhou no servidor e o servidor enviou um código de status de erro. Por exemplo, o cliente gRPC iniciou uma chamada que não tinha dados obrigatórios da mensagem de solicitação e o servidor retorna um código de status INVALID_ARGUMENT.
  • Ocorreu um erro no cliente durante uma chamada gRPC. Por exemplo, um cliente faz uma chamada gRPC, não consegue se conectar ao servidor e gera um erro com o status UNAVAILABLE.
  • O CancellationToken transmitido para a chamada gRPC é cancelado. A chamada gRPC é interrompida e o cliente gera um erro com o status CANCELLED.
  • Uma chamada gRPC excede o prazo configurado. A chamada gRPC é interrompida e o cliente gera um erro com o status DEADLINE_EXCEEDED.

Tratamento avançado de erro

O tratamento avançado de erro permite que informações complexas e estruturadas sejam enviadas com mensagens de erro. Por exemplo, a validação de campos de mensagem de entrada que retorna uma lista de nomes de campos inválidos e descrições. O modelo de erro google.rpc.Status costuma ser usado para enviar informações de erro complexas entre aplicativos gRPC.

O gRPC no .NET dá suporte a um modelo de erro avançado por meio do pacote Grpc.StatusProto. Esse pacote contém métodos para criar modelos de erros avançados no servidor e lê-los por um cliente. O modelo de erro avançado se baseia nos recursos de tratamento internos do gRPC, que podem ser usados lado a lado.

Importante

Os erros são incluídos nos cabeçalhos e, em geral, o total de cabeçalhos nas respostas é limitado a 8 KB (8.192 bytes). Verifique se os cabeçalhos que contêm erros não excedem 8 KB.

Como criar erros avançados no servidor

Erros avançados são criados por meio do Google.Rpc.Status. Esse tipo é diferente de Grpc.Core.Status.

O Google.Rpc.Status tem campos de status, mensagem e detalhes. O campo mais importante é o chamado Detalhes, que é um campo repetido de valores Any. Os detalhes são o local em que um conteúdo complexo é adicionado.

Embora qualquer tipo de mensagem possa ser usado como um conteúdo, recomendamos usar um dos conteúdos de erro padrão:

  • BadRequest
  • PreconditionFailure
  • ErrorInfo
  • ResourceInfo
  • QuotaFailure

O Grpc.StatusProto inclui o ToRpcException, um método auxiliar usado para converter Google.Rpc.Status em um erro. Gere o erro no método do 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();
        }
    }
}

Como ler erros avançados por um cliente

Os erros avançados são lidos por meio da RpcException capturada no cliente. Capture a exceção e use métodos auxiliares fornecidos por Grpc.StatusCode para obter a respectiva instância 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}");
        }
    }
}

O código anterior:

  • Faz uma chamada gRPC dentro de um try/catch que captura RpcException.
  • Chama GetRpcStatus() para tentar obter o modelo de erro avançado da exceção.
  • Chama GetDetail<BadRequest>() para tentar obter um conteúdo BadRequest do erro avançado.

Recursos adicionais