Share via


Intercettori gRPC in .NET

Di Ernest Nguyen

Gli intercettori sono un concetto gRPC che consente alle app di interagire con le chiamate gRPC in ingresso o in uscita. Offrono un modo per arricchire la pipeline di elaborazione delle richieste.

Gli intercettori sono configurati per un canale o un servizio ed eseguiti automaticamente per ogni chiamata gRPC. Poiché gli intercettori sono trasparenti per la logica dell'applicazione dell'utente, sono una soluzione eccellente per i casi comuni, ad esempio registrazione, monitoraggio, autenticazione e convalida.

Tipo diInterceptor

Gli intercettori possono essere implementati sia per i server gRPC che per i client creando una classe che eredita dal Interceptor tipo :

public class ExampleInterceptor : Interceptor
{
}

Per impostazione predefinita, la Interceptor classe base non esegue alcuna operazione. Aggiungere il comportamento a un intercettore eseguendo l'override dei metodi della classe di base appropriati in un'implementazione dell'intercettore.

Intercettori client

Gli intercettori client gRPC intercettano le chiamate RPC in uscita. Forniscono l'accesso alla richiesta inviata, alla risposta in ingresso e al contesto per una chiamata lato client.

Interceptor metodi di override per il client:

  • BlockingUnaryCall: intercetta una chiamata di blocco di una RPC unaria.
  • AsyncUnaryCall: intercetta una chiamata asincrona di una RPC unaria.
  • AsyncClientStreamingCall: intercetta una chiamata asincrona di una RPC di streaming client.
  • AsyncServerStreamingCall: intercetta una chiamata asincrona di una RPC in streaming server.
  • AsyncDuplexStreamingCall: intercetta una chiamata asincrona di una RPC di streaming bidirezionale.

Avviso

Anche se entrambi BlockingUnaryCall e AsyncUnaryCall fanno riferimento a RPC unari, non sono intercambiabili. Una chiamata di blocco non viene intercettata da AsyncUnaryCalle una chiamata asincrona non viene intercettata da un oggetto BlockingUnaryCall.

Creare un intercettore gRPC client

Il codice seguente presenta un esempio di base dell'intercettazione di una chiamata asincrona di una chiamata unaria:

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

Override di AsyncUnaryCall:

  • Intercetta una chiamata unaria asincrona.
  • Registra informazioni dettagliate sulla chiamata.
  • Chiama il continuation parametro passato al metodo . In questo modo viene richiamato l'intercettore successivo nella catena o il chiamante sottostante se si tratta dell'ultimo intercettore.

I metodi su Interceptor per ogni tipo di metodo di servizio hanno firme diverse. Tuttavia, il concetto alla base continuation dei parametri e context rimane invariato:

  • continuation è un delegato che richiama l'intercettore successivo nella catena o il chiamante sottostante (se nella catena non è rimasto alcun intercettore). Non è un errore chiamarlo zero o più volte. Gli intercettori non devono restituire una rappresentazione di chiamata (AsyncUnaryCall in caso di RPC unario) restituita dal continuation delegato. Omettendo la chiamata del delegato e restituendo la propria istanza della rappresentazione di chiamata, la catena degli intercettori viene interrotta e viene restituita immediatamente la risposta associata.
  • context contiene valori con ambito associati alla chiamata sul lato client. Usare context per passare metadati, ad esempio entità di sicurezza, credenziali o dati di traccia. Inoltre, context contiene informazioni sulle scadenze e l'annullamento. Per altre informazioni, vedere Reliable gRPC services with deadline and cancellation .For more information, see Reliable gRPC services with deadline and cancellation.

Attesa della risposta nell'intercettore client

Un intercettore può attendere la risposta nelle chiamate di streaming unario e client aggiornando il AsyncUnaryCall<TResponse>.ResponseAsync valore o AsyncClientStreamingCall<TRequest, TResponse>.ResponseAsync .

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

Il codice precedente:

  • Crea un nuovo intercettore che esegue l'override AsyncUnaryCalldi .
  • Override di AsyncUnaryCall:
    • Chiama il continuation parametro per richiamare l'elemento successivo nella catena dell'intercettore.
    • Crea una nuova AsyncUnaryCall<TResponse> istanza in base al risultato della continuazione.
    • Esegue il wrapping dell'attività ResponseAsync usando il HandleResponse metodo .
    • Attende la risposta con HandleResponse. In attesa della risposta è possibile aggiungere la logica dopo che il client ha ricevuto la risposta. Attendendo la risposta in un blocco try-catch, è possibile registrare gli errori delle chiamate.

Per altre informazioni su come creare un intercettore client, vedere l'esempio ClientLoggerInterceptor.cs nel grpc/grpc-dotnet repository GitHub.

Configurare gli intercettori client

Gli intercettori client gRPC sono configurati in un canale.

Il codice seguente:

  • Crea un canale usando GrpcChannel.ForAddress.
  • Usa il Intercept metodo di estensione per configurare il canale per l'uso dell'intercettore. Si noti che questo metodo restituisce un oggetto CallInvoker. I client gRPC fortemente tipizzato possono essere creati da un invoker proprio come un canale.
  • Crea un client dal invoker. Chiamate gRPC effettuate dal client eseguono automaticamente l'intercettore.
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var invoker = channel.Intercept(new ClientLoggerInterceptor());

var client = new Greeter.GreeterClient(invoker);

Il Intercept metodo di estensione può essere concatenato per configurare più intercettori per un canale. In alternativa, è presente un Intercept overload che accetta più intercettori. È possibile eseguire un numero qualsiasi di intercettori per una singola chiamata gRPC, come illustrato nell'esempio seguente:

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

Gli intercettori vengono richiamati in ordine inverso dei metodi di estensione concatenati Intercept . Nel codice precedente, gli intercettori vengono richiamati nell'ordine seguente:

  1. ClientLoggerInterceptor
  2. ClientMonitoringInterceptor
  3. ClientTokenInterceptor

Per informazioni su come configurare gli intercettori con la factory client gRPC, vedere integrazione della factory client gRPC in .NET.

Intercettori server

Gli intercettori del server gRPC intercettano le richieste RPC in ingresso. Forniscono l'accesso alla richiesta in ingresso, alla risposta in uscita e al contesto per una chiamata lato server.

Interceptor metodi di override per il server:

  • UnaryServerHandler: intercetta unario RPC.
  • ClientStreamingServerHandler: intercetta una RPC in streaming client.
  • ServerStreamingServerHandler: intercetta una RPC in streaming server.
  • DuplexStreamingServerHandler: intercetta una RPC bidirezionale in streaming.

Creare un intercettore gRPC del server

Il codice seguente presenta un esempio di intercettazione di una RPC unaria in ingresso:

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

Override di UnaryServerHandler:

  • Intercetta una chiamata unaria in ingresso.
  • Registra informazioni dettagliate sulla chiamata.
  • Chiama il continuation parametro passato al metodo . In questo modo viene richiamato l'intercettore successivo nella catena o nel gestore del servizio se si tratta dell'ultimo intercettore.
  • Registra eventuali eccezioni. In attesa della continuazione è possibile aggiungere la logica dopo l'esecuzione del metodo del servizio. Attendendo la continuazione in un blocco try-catch, è possibile registrare gli errori dei metodi.

La firma dei metodi di intercettore client e server è simile:

  • continuation indica un delegato per un RPC in ingresso che chiama l'intercettore successivo nella catena o il gestore del servizio (se nella catena non è presente alcun intercettore). Analogamente agli intercettori client, è possibile chiamarla in qualsiasi momento e non è necessario restituire una risposta direttamente dal delegato di continuazione. La logica in uscita può essere aggiunta dopo l'esecuzione di un gestore del servizio in attesa della continuazione.
  • context contiene metadati associati alla chiamata sul lato server, ad esempio metadati della richiesta, scadenze e annullamento o risultato RPC.

Per altre informazioni su come creare un intercettore server, vedere l'esempio ServerLoggerInterceptor.cs nel grpc/grpc-dotnet repository GitHub.

Configurare gli intercettori del server

Gli intercettori del server gRPC vengono configurati all'avvio. Il codice seguente:

  • Aggiunge gRPC all'app con AddGrpc.
  • ServerLoggerInterceptor Configura per tutti i servizi aggiungendolo alla raccolta dell'opzione di Interceptors servizio.
public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc(options =>
    {
        options.Interceptors.Add<ServerLoggerInterceptor>();
    });
}

È anche possibile configurare un intercettore per un servizio specifico usando AddServiceOptions e specificando il tipo di servizio.

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

Gli intercettori vengono eseguiti nell'ordine in cui vengono aggiunti a InterceptorCollection. Se sono configurati intercettori globali e singoli, gli intercettori configurati a livello globale vengono eseguiti prima di quelli configurati per un singolo servizio.

Per impostazione predefinita, gli intercettori del server gRPC hanno una durata per richiesta. L'override di questo comportamento è possibile tramite la registrazione del tipo di intercettore con inserimento delle dipendenze. L'esempio seguente registra l'oggetto ServerLoggerInterceptor con una durata singleton:

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

    services.AddSingleton<ServerLoggerInterceptor>();
}

Intercettori gRPC e middleware

ASP.NET middleware Core offre funzionalità simili rispetto agli intercettori nelle app gRPC basate su C core. ASP.NET middleware core e gli intercettori sono concettualmente simili. Entrambe le opzioni:

  • Vengono usati per costruire una pipeline che gestisce una richiesta gRPC.
  • Consentire l'esecuzione del lavoro prima o dopo il componente successivo nella pipeline.
  • Fornire l'accesso a HttpContext:
    • Nel middleware, è HttpContext un parametro .
    • Negli intercettori è HttpContext possibile accedere a utilizzando il ServerCallContext parametro con il metodo di ServerCallContext.GetHttpContext estensione. Questa funzionalità è specifica per gli intercettori in esecuzione in ASP.NET Core.

Differenze tra l'intercettore gRPC e il middleware core ASP.NET:

  • Intercettori:
    • Operare sul livello gRPC dell'astrazione usando .ServerCallContext
    • Fornire l'accesso a:
      • Messaggio deserializzato inviato a una chiamata.
      • Messaggio restituito dalla chiamata prima della serializzazione.
    • Può intercettare e gestire le eccezioni generate dai servizi gRPC.
  • Middleware:
    • Viene eseguito per tutte le richieste HTTP.
    • Viene eseguito prima degli intercettori gRPC.
    • Opera sui messaggi HTTP/2 sottostanti.
    • Può accedere solo ai byte dai flussi di richiesta e risposta.

Risorse aggiuntive