Felhantering med gRPC

Anmärkning

Det här är inte den senaste versionen av den här artikeln. Den aktuella versionen finns i .NET 10-versionen av den här artikeln.

Varning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i supportpolicyn för .NET och .NET Core. Den aktuella versionen finns i .NET 10-versionen av den här artikeln.

Av James Newton-King

I den här artikeln beskrivs felhantering och gRPC:

  • Inbyggda funktioner för felhantering med hjälp av gRPC-statuskoder och felmeddelanden.
  • Skicka komplex, strukturerad felinformation med hjälp av omfattande felhantering.

Inbyggd felhantering

gRPC-anrop kommunicerar lyckade eller misslyckade utfall med en statuskod. När ett gRPC-anrop har slutförts returnerar servern en OK status till klienten. Om ett fel inträffar returnerar gRPC:

  • En felstatuskod, till exempel CANCELLED eller UNAVAILABLE.
  • Ett valfritt strängfelmeddelande.

De typer som ofta används med felhantering är:

  • StatusCode: En uppräkning av gRPC-statuskoder. OK signalerar framgång; andra värden innebär misslyckande.
  • Status: A struct som kombinerar ett StatusCode och ett valfritt strängfelmeddelande. Felmeddelandet innehåller ytterligare information om vad som hände.
  • RpcException: En undantagstyp som har Status ett värde. Det här undantaget genereras i gRPC-servermetoder och fångas av gRPC-klienter.

Inbyggd felhantering stöder endast en statuskod och en strängbeskrivning. Om du vill skicka komplex felinformation från servern till klienten använder du omfattande felhantering.

Utlösa serverfel

Ett gRPC-serveranrop returnerar alltid status. Servern returnerar OK automatiskt när en metod har slutförts.

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

Föregående kod:

  • Implementerar den unära metoden SayHello som slutförs framgångsrikt när den returnerar ett svarsmeddelande.
  • Implementerar serverströmningsmetoden SayHelloStreaming som framgångsrikt slutförs när metoden är klar.

Serverfelstatus

gRPC-metoder returnerar en felstatuskod genom att utlösa ett undantag. När en RpcException genereras på servern returneras dess statuskod och beskrivning till klienten:

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

Undantagstyper som inte RpcException också orsakar att anropet misslyckas, men med en UNKNOWN statuskod och ett allmänt meddelande Exception was thrown by handler.

Exception was thrown by handler skickas till klienten i stället för undantagsmeddelandet för att förhindra att potentiellt känslig information exponeras. Om du vill se ett mer beskrivande felmeddelande i en utvecklingsmiljö konfigurerar du EnableDetailedErrors.

Hantera klientfel

När en gRPC-klient gör ett anrop verifieras statuskoden automatiskt när svaret nås. Om du till exempel väntar på ett unary gRPC-anrop returneras meddelandet som skickas av servern om anropet lyckas och utlöser ett RpcException om det uppstår ett fel. Hantera RpcException för att fånga fel i en klient:

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

Föregående kod:

  • Gör ett unary gRPC-anrop till SayHello metoden.
  • Skriver svarsmeddelandet till konsolen om det lyckas.
  • Upptäcker RpcException och skriver ut felinformation vid misslyckande.

Felscenarier

Fel representeras av RpcException med en felstatuskod och ett valfritt informationsmeddelande. RpcException genereras i många scenarier:

  • Anropet misslyckades på servern och servern skickade en felstatuskod. Till exempel startade gRPC-klienten ett anrop som saknade nödvändiga data från begärandemeddelandet och servern returnerar en INVALID_ARGUMENT statuskod.
  • Ett fel uppstod i klienten när gRPC-anropet gjordes. En klient gör till exempel ett gRPC-anrop, kan inte ansluta till servern och utlöser ett fel med statusen UNAVAILABLE.
  • Det CancellationToken som skickas till gRPC-anropet avbryts. GRPC-anropet stoppas och klienten genererar ett fel med statusen CANCELLED.
  • Ett gRPC-anrop överskrider den konfigurerade tidsgränsen. GRPC-anropet stoppas och klienten genererar ett fel med statusen DEADLINE_EXCEEDED.

Omfattande felhantering

Omfattande felhantering gör att komplex, strukturerad information kan skickas med felmeddelanden. Till exempel validering av inkommande meddelandefält som returnerar en lista med ogiltiga fältnamn och beskrivningar. Felmodellengoogle.rpc.Status används ofta för att skicka komplex felinformation mellan gRPC-appar.

gRPC på .NET stöder en omfattande felmodell med hjälp av Grpc.StatusProto paketet. Det här paketet har metoder för att skapa omfattande felmodeller på servern och läsa dem av en klient. Den omfattande felmodellen bygger på gRPC:s inbyggda hanteringsfunktioner och kan användas sida vid sida.

Viktigt!

Fel ingår i rubriker och de totala rubrikerna i svar är ofta begränsade till 8 KB (8 192 byte). Kontrollera att rubrikerna som innehåller fel inte överskrider 8 KB.

Skapa omfattande fel på servern

Omfattande fel skapas från Google.Rpc.Status. Den här typen skiljer sig från Grpc.Core.Status.

Google.Rpc.Status har fälten status, meddelande och information. Det viktigaste fältet är information, vilket är ett upprepande fält med Any värden. Information är var komplexa nyttolaster läggs till.

Även om alla meddelandetyper kan användas som en nyttolast rekommenderar vi att du använder någon av standardfelnyttolasterna:

  • BadRequest
  • PreconditionFailure
  • ErrorInfo
  • ResourceInfo
  • QuotaFailure

Grpc.StatusProto ToRpcException innehåller en hjälpfunktion för att omvandla Google.Rpc.Status till ett fel. Generera felet från gRPC-servermetoden:

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

Avläsning av detaljerade fel från en klient

Detaljerade fel läses från RpcException som fångas av klienten. Fånga undantaget och använd hjälpmetoder som tillhandahålls av Grpc.StatusCode för att hämta dess Google.Rpc.Status instans:

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

Föregående kod:

  • Gör ett gRPC-anrop i en try/catch som fångar RpcException.
  • Anropar GetRpcStatus() för att försöka få fram den fylliga felmodellen från undantaget.
  • Anrop GetDetail<BadRequest>() för att försöka hämta en BadRequest nyttolast från det omfattande felet.

Ytterligare resurser