De gRPC-interceptors op .NET

Opmerking

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 10-versie van dit artikel voor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie de .NET- en .NET Core-ondersteuningsbeleidvoor meer informatie. Zie de .NET 10-versie van dit artikel voor de huidige release.

Door Ernest Nguyen

Interceptors zijn een gRPC-concept waarmee apps kunnen communiceren met binnenkomende of uitgaande gRPC-aanroepen. Ze bieden een manier om de pijplijn voor aanvraagverwerking te verrijken.

Interceptors worden geconfigureerd voor een kanaal of service en automatisch uitgevoerd voor elke gRPC-aanroep. Omdat interceptors transparant zijn voor de toepassingslogica van de gebruiker, zijn ze een uitstekende oplossing voor veelvoorkomende gevallen, zoals logboekregistratie, bewaking, verificatie en validatie.

Interceptor soort

Interceptors kunnen worden geïmplementeerd voor zowel gRPC-servers als clients door een klasse te maken die overkomt van het Interceptor type:

public class ExampleInterceptor : Interceptor
{
}

Standaard doet de Interceptor basisklasse niets. Voeg gedrag toe aan een interceptor door de juiste basisklassemethoden in een interceptor-implementatie te overschrijven.

Interceptors voor klanten

gRPC-client interceptors onderscheppen uitgaande RPC-aanroepen. Ze bieden toegang tot de verzonden aanvraag, het binnenkomende antwoord en de context voor een aanroep aan de clientzijde.

Interceptor methoden om te overschrijven voor client:

  • BlockingUnaryCall: Onderschept een blokkerende aanroep van een unaire RPC.
  • AsyncUnaryCall: Onderschept een asynchrone aanroep van een unaire RPC.
  • AsyncClientStreamingCall: Onderschept een asynchrone aanroep van een clientstreaming RPC.
  • AsyncServerStreamingCall: Onderschept een asynchrone oproep van een server-streaming RPC.
  • AsyncDuplexStreamingCall: Onderschept een asynchrone aanroep van een bidirectionele streaming RPC.

Waarschuwing

Hoewel beide BlockingUnaryCall en AsyncUnaryCall verwijzen naar unaire RPC's, zijn ze niet uitwisselbaar. Een blokkerende aanroep wordt niet onderschept door AsyncUnaryCallen een asynchrone aanroep wordt niet onderschept door een BlockingUnaryCall.

Een client gRPC-interceptor maken

De volgende code bevat een basisvoorbeeld van het onderscheppen van een asynchrone aanroep van een unaire aanroep:

public class ClientLoggingInterceptor : Interceptor
{
    private readonly ILogger _logger;

    public ClientLoggingInterceptor(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<ClientLoggingInterceptor>();
    }

    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request,
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
    {
        _logger.LogInformation("Starting call. Type/Method: {Type} / {Method}",
            context.Method.Type, context.Method.Name);
        return continuation(request, context);
    }
}

Overschrijven AsyncUnaryCall:

  • Onderschept een asynchrone unaire aanroep.
  • Registreert details over de oproep.
  • Roept de continuation parameter aan die is doorgegeven aan de methode. Hiermee wordt de volgende interceptor in de keten of de onderliggende aanroeper aangeroepen als dit de laatste interceptor is.

Interceptor Methoden voor elk type servicemethode hebben verschillende handtekeningen. Het concept achter continuation en context parameters blijven echter hetzelfde:

  • continuation is een gemachtigde die de volgende interceptor aanroept in de keten of de onderliggende aanroeper (als er geen interceptor in de keten is). Het is geen fout om het nul of meerdere keren aan te roepen. Interceptoren zijn niet verplicht om een oproepweergave te retourneren (AsyncUnaryCall in het geval van een unaire RPC) die is geretourneerd door de continuation delegate. Als u de gedelegeerde oproep weglaat en uw eigen exemplaar van de aanroeprepresentatie retourneert, wordt de interceptorketen verbroken en wordt het bijbehorende antwoord onmiddellijk geretourneerd.
  • context bevat bereikwaarden die zijn gekoppeld aan de aanroep aan de clientzijde. Hiermee context geeft u metagegevens door, zoals beveiligingsprinciplen, referenties of traceringsgegevens. context Bovendien bevat informatie over deadlines en annulering. Voor meer informatie, zie Betrouwbare gRPC-services met deadlines en annulering.

Wachtend op antwoord in client interceptor

Een interceptor kan wachten op het antwoord in unaire en clientstreaming-aanroepen door de AsyncUnaryCall<TResponse>.ResponseAsync of AsyncClientStreamingCall<TRequest, TResponse>.ResponseAsync waarde bij te werken.

public class ErrorHandlerInterceptor : Interceptor
{
    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request,
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
    {
        var call = continuation(request, context);

        return new AsyncUnaryCall<TResponse>(
            HandleResponse(call.ResponseAsync),
            call.ResponseHeadersAsync,
            call.GetStatus,
            call.GetTrailers,
            call.Dispose);
    }

    private async Task<TResponse> HandleResponse<TResponse>(Task<TResponse> inner)
    {
        try
        {
            return await inner;
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException("Custom error", ex);
        }
    }
}

De voorgaande code:

  • Maakt een nieuwe interceptor die AsyncUnaryCall overschrijft.
  • Overschrijven AsyncUnaryCall:
    • Roept de continuation parameter aan om het volgende item in de interceptorketen aan te roepen.
    • Hiermee maakt u een nieuw AsyncUnaryCall<TResponse> exemplaar op basis van het resultaat van de voortzetting.
    • Verpakt de ResponseAsync taak met behulp van de HandleResponse methode.
    • Wacht op het antwoord met HandleResponse. In afwachting van het antwoord kan logica worden toegevoegd nadat de client het antwoord heeft ontvangen. Door te wachten op het antwoord in een try-catch-blok, kunnen fouten van aanroepen worden vastgelegd.

Zie het voorbeeld in de ClientLoggerInterceptor.cs GitHub-opslagplaats voor meer informatie over hetgrpc/grpc-dotnet maken van een client-interceptor.

Client interceptors configureren

gRPC-client interceptors worden geconfigureerd in een kanaal.

De volgende code:

  • Hiermee maakt u een kanaal met behulp van GrpcChannel.ForAddress.
  • Gebruikt de Intercept extensiemethode om het kanaal te configureren voor het gebruik van de interceptor. Houd er rekening mee dat deze methode een CallInvoker retourneert. Sterk getypeerde gRPC-clients kunnen worden gemaakt vanuit een aanroeper net als een kanaal.
  • Hiermee maakt u een client van de aanroeper. gRPC-aanroepen die door de client worden gedaan, voeren automatisch de interceptor uit.
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var invoker = channel.Intercept(new ClientLoggerInterceptor());

var client = new Greeter.GreeterClient(invoker);

De Intercept extensiemethode kan worden gekoppeld om meerdere interceptors voor een kanaal te configureren. Er is ook een Intercept overload die meerdere interceptoren accepteert. Een willekeurig aantal interceptors kan worden uitgevoerd voor één gRPC-aanroep, zoals in het volgende voorbeeld wordt gedemonstreerd:

var invoker = channel
    .Intercept(new ClientTokenInterceptor())
    .Intercept(new ClientMonitoringInterceptor())
    .Intercept(new ClientLoggerInterceptor());

Interceptors worden aangeroepen in omgekeerde volgorde van de gekoppelde Intercept extensiemethoden. In de voorgaande code worden interceptors aangeroepen in de volgende volgorde:

  1. ClientLoggerInterceptor
  2. ClientMonitoringInterceptor
  3. ClientTokenInterceptor

Zie gRPC-clientfactory-integratie in .NET voor meer informatie over het configureren van interceptors met gRPC-clientfactory.

Server-onderscheppers

gRPC-server interceptors onderscheppen binnenkomende RPC-aanvragen. Ze bieden toegang tot de binnenkomende aanvraag, het uitgaande antwoord en de context voor een aanroep aan de serverzijde.

Interceptor methoden om te overschrijven voor de server:

  • UnaryServerHandler: Onderschept een unaire RPC.
  • ClientStreamingServerHandler: Onderschept een RPC voor clientstreaming.
  • ServerStreamingServerHandler: Onderschept een server-streaming RPC.
  • DuplexStreamingServerHandler: Onderschept een RPC in twee richtingen.

Een server gRPC-interceptor maken

De volgende code toont een voorbeeld van het onderscheppen van een inkomende unaire RPC.

public class ServerLoggerInterceptor : Interceptor
{
    private readonly ILogger _logger;

    public ServerLoggerInterceptor(ILogger<ServerLoggerInterceptor> logger)
    {
        _logger = logger;
    }

    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
        TRequest request,
        ServerCallContext context,
        UnaryServerMethod<TRequest, TResponse> continuation)
    {
        _logger.LogInformation("Starting receiving call. Type/Method: {Type} / {Method}",
            MethodType.Unary, context.Method);
        try
        {
            return await continuation(request, context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error thrown by {context.Method}.");
            throw;
        }
    }
}

Overschrijven UnaryServerHandler:

  • Onderschept een inkomende unaire oproep.
  • Registreert details over de oproep.
  • Roept de continuation parameter aan die is doorgegeven aan de methode. Hiermee wordt de volgende interceptor in de keten aangeroepen, of de servicemanager als dit de laatste interceptor is.
  • Registreert eventuele uitzonderingen. Door te wachten op het vervolg kan logica worden toegevoegd nadat de servicemethode is uitgevoerd. Door te wachten op de voortzetting in een try-catch-blok, kunnen fouten van methoden worden vastgelegd.

De handtekening van zowel client- als server interceptors methoden zijn vergelijkbaar:

  • continuation staat voor een vertegenwoordiger voor een binnenkomende RPC die de volgende interceptor oproept binnen de keten of de servicehandler (als er geen andere interceptor over is). Net als bij client-interceptors kunt u deze op elk gewenst moment aanroepen en hoeft u geen antwoord rechtstreeks van de vervolgdelegatie te retourneren. Uitgaande logica kan worden toegevoegd nadat een servicehandler is uitgevoerd door te wachten op de voortzetting.
  • context bevat metagegevens die zijn gekoppeld aan de aanroep aan de serverzijde, zoals metagegevens van aanvragen, deadlines en annulering of RPC-resultaat.

Zie het voorbeeld in de ServerLoggerInterceptor.cs GitHub-opslagplaats voor meer informatie over hetgrpc/grpc-dotnet maken van een server-interceptor.

Server interceptors configureren

gRPC-server-interceptors worden geconfigureerd bij het opstarten. De volgende code:

  • Voegt gRPC toe aan de app met AddGrpc.
  • Configureert ServerLoggerInterceptor voor alle services door deze toe te voegen aan de verzameling van Interceptors de serviceoptie.
public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc(options =>
    {
        options.Interceptors.Add<ServerLoggerInterceptor>();
    });
}

Een interceptor kan ook worden geconfigureerd voor een specifieke service door het servicetype te gebruiken AddServiceOptions en op te geven.

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddGrpc()
        .AddServiceOptions<GreeterService>(options =>
        {
            options.Interceptors.Add<ServerLoggerInterceptor>();
        });
}

Interceptors worden uitgevoerd in de volgorde waarin ze worden toegevoegd aan de InterceptorCollection. Als zowel globale als enkele service-interceptors zijn geconfigureerd, worden wereldwijd geconfigureerde interceptors uitgevoerd voordat ze zijn geconfigureerd voor één service.

GRPC-server interceptors hebben standaard een levensduur per aanvraag. Het overschrijven van dit gedrag is mogelijk door het interceptor type te registreren met afhankelijkheidsinjectie. In het volgende voorbeeld wordt de singleton-levensduur ServerLoggerInterceptor geregistreerd:

public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc(options =>
    {
        options.Interceptors.Add<ServerLoggerInterceptor>();
    });

    services.AddSingleton<ServerLoggerInterceptor>();
}

gRPC-interceptors tegenover middleware

ASP.NET Core middleware biedt vergelijkbare functies in vergelijking met interceptors in GRPC-apps op basis van C-core. ASP.NET Core middleware en interceptors zijn conceptueel vergelijkbaar. Beide:

  • Worden gebruikt om een pijplijn te maken die een gRPC-aanvraag verwerkt.
  • Toestaan dat werk wordt uitgevoerd vóór of na het volgende onderdeel in de pijplijn.
  • Geef toegang tot HttpContext:
    • In middleware is dit HttpContext een parameter.
    • In interceptors kan men de HttpContext benaderen door de ServerCallContext parameter te gebruiken met de ServerCallContext.GetHttpContext extensiemethode. Deze functie is specifiek voor interceptors die worden uitgevoerd in ASP.NET Core.

gRPC Interceptor verschillen van ASP.NET Core middleware:

  • Onderscheppers:
    • Gebruik de gRPC-laag van abstractie met behulp van de ServerCallContext.
    • Toegang bieden tot:
      • Het gedeserialiseerde bericht dat naar een oproep is verzonden.
      • Het bericht dat is geretourneerd vanuit de aanroep voordat het wordt geserialiseerd.
    • Kan uitzonderingen van gRPC-services opvangen en afhandelen.
  • Middleware:
    • Wordt uitgevoerd voor alle HTTP-aanvragen.
    • Wordt uitgevoerd vóór gRPC-interceptors.
    • Werkt op de onderliggende HTTP/2-berichten.
    • Kan alleen toegang krijgen tot bytes vanuit de aanvraag- en antwoordstreams.

Aanvullende bronnen