Share via


Intercepteurs gRPC sur .NET

Par Ernest Nguyen

Les intercepteurs sont un concept gRPC qui permet aux applications d’interagir avec les appels gRPC entrants ou sortants. Ils offrent un moyen d’enrichir le pipeline de traitement des requêtes.

Les intercepteurs sont configurés pour un canal ou un service et exécutés automatiquement pour chaque appel gRPC. Étant donné que les intercepteurs sont transparents par rapport à la logique d’application de l’utilisateur, ils constituent une excellente solution pour les cas courants, comme la journalisation, la surveillance, l’authentification et la validation.

TypeInterceptor

Les intercepteurs peuvent être implémentés pour les serveurs et les clients gRPC en créant une classe qui hérite du type Interceptor :

public class ExampleInterceptor : Interceptor
{
}

Par défaut, la classe de base Interceptor ne fait rien. Ajoutez un comportement à un intercepteur en remplaçant les méthodes de classe de base appropriées dans une implémentation d’intercepteur.

Intercepteurs clients

Les intercepteurs clients gRPC interceptent les appels RPC sortants. Ils fournissent l’accès à la requête envoyée, à la réponse entrante et au contexte d’un appel côté client.

Méthodes Interceptor à remplacer pour le client :

  • BlockingUnaryCall : intercepte un appel bloquant d’un RPC unaire.
  • AsyncUnaryCall : intercepte un appel asynchrone d’un RPC unaire.
  • AsyncClientStreamingCall : intercepte un appel asynchrone d’un RPC de diffusion en continu client.
  • AsyncServerStreamingCall : intercepte un appel asynchrone d’un RPC de diffusion en continu de serveur.
  • AsyncDuplexStreamingCall : intercepte un appel asynchrone d’un RPC de diffusion en continu bidirectionnel.

Avertissement

Bien que BlockingUnaryCall et AsyncUnaryCall fassent référence à des RPC unaires, ils ne sont pas interchangeables. Un appel bloquant n’est pas intercepté par AsyncUnaryCall, et un appel asynchrone n’est pas intercepté par BlockingUnaryCall.

Créer un intercepteur gRPC client

Le code suivant présente un exemple de base d’interception d’un appel asynchrone d’un appel unaire :

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

Remplacement de AsyncUnaryCall :

  • Intercepte un appel unaire asynchrone.
  • Consigne les détails de l’appel.
  • Appelle le paramètre continuation passé dans la méthode. Cela appelle l’intercepteur suivant dans la chaîne ou l’appelant sous-jacent s’il s’agit du dernier intercepteur.

Les méthodes sur Interceptor pour chaque type de méthode de service ont des signatures différentes. Toutefois, le concept derrière les paramètres continuation et context reste le même :

  • continuation est un délégué qui appelle l’intercepteur suivant dans la chaîne ou l’appelant sous-jacent (s’il n’y a plus d’intercepteur dans la chaîne). Ce n’est pas une erreur de l’appeler zéro ou plusieurs fois. Les intercepteurs ne sont pas tenus de retourner une représentation d’appel (AsyncUnaryCall en cas de RPC unaire) retournée par le délégué continuation. L’omission de l’appel délégué et le retour de votre propre instance de représentation d’appel interrompt la chaîne des intercepteurs et retourne immédiatement la réponse associée.
  • context comporte des valeurs délimitées associées à l’appel côté client. Utilisez context pour transmettre des métadonnées, comme des principaux de sécurité, des informations d’identification ou des données de suivi. En outre, context contient des informations sur les échéances et l’annulation. Pour plus d’informations, consultez Services gRPC fiables avec des échéances et des annulations.

Attente de réponse dans l’intercepteur client

Un intercepteur peut attendre la réponse dans les appels unaires et de diffusion en continu client en mettant à jour la valeur AsyncUnaryCall<TResponse>.ResponseAsync ou 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);
        }
    }
}

Le code précédent :

  • Crée un intercepteur qui remplace AsyncUnaryCall.
  • Remplacement de AsyncUnaryCall :
    • Appelle le paramètre continuation pour appeler l’élément suivant dans la chaîne d’intercepteurs.
    • Crée une instance AsyncUnaryCall<TResponse> basée sur le résultat de la continuation.
    • Encapsule la tâche ResponseAsync à l’aide de la méthode HandleResponse.
    • Attend la réponse avec HandleResponse. L’attente de la réponse permet d’ajouter une logique après la réception de la réponse par le client. En attendant la réponse dans un bloc try-catch, les erreurs des appels peuvent être consignées.

Pour plus d’informations sur la création d’un intercepteur client, consultez l’exemple ClientLoggerInterceptor.cs dans le dépôt GitHub grpc/grpc-dotnet.

Configurer des intercepteurs clients

Les intercepteurs clients gRPC sont configurés sur un canal.

Le code suivant :

  • Crée un canal à l’aide de GrpcChannel.ForAddress.
  • Utilise la méthode d’extension Intercept pour configurer le canal pour utiliser l’intercepteur. Notez que cette méthode retourne un CallInvoker. Les clients gRPC fortement typés peuvent être créés à partir d’un appelant comme un canal.
  • Crée un client à partir de l’appelant. Les appels gRPC effectués par le client exécutent automatiquement l’intercepteur.
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var invoker = channel.Intercept(new ClientLoggerInterceptor());

var client = new Greeter.GreeterClient(invoker);

La méthode d’extension Intercept peut être chaînée pour configurer plusieurs intercepteurs pour un canal. Vous pouvez également utiliser une surcharge Intercept qui accepte plusieurs intercepteurs. Un nombre quelconque d’intercepteurs peut être exécuté pour un seul appel gRPC, comme le montre l’exemple suivant :

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

Les intercepteurs sont appelés dans l’ordre inverse des méthodes d’extension Intercept chaînées. Dans le code précédent, les intercepteurs sont appelés dans l’ordre suivant :

  1. ClientLoggerInterceptor
  2. ClientMonitoringInterceptor
  3. ClientTokenInterceptor

Pour plus d’informations sur la configuration des intercepteurs avec la fabrique de clients gRPC, consultez Intégration de la fabrique de clients gRPC dans .NET.

Intercepteurs de serveur

Les intercepteurs de serveur gRPC interceptent les requêtes RPC entrantes. Ils fournissent l’accès à la requête entrante, à la réponse sortante et au contexte d’un appel côté serveur.

Méthodes Interceptor à remplacer pour le serveur :

  • UnaryServerHandler : intercepte un RPC unaire.
  • ClientStreamingServerHandler : intercepte un RPC de diffusion en continu client.
  • ServerStreamingServerHandler : intercepte un RPC de diffusion en continu de serveur.
  • DuplexStreamingServerHandler : intercepte un RPC de diffusion en continu bidirectionnel.

Créer un intercepteur gRPC de serveur

Le code suivant présente un exemple d’interception d’un RPC unaire entrant :

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

Remplacement de UnaryServerHandler :

  • Intercepte un appel unaire entrant.
  • Consigne les détails de l’appel.
  • Appelle le paramètre continuation passé dans la méthode. Cela appelle l’intercepteur suivant dans la chaîne ou le gestionnaire de service s’il s’agit du dernier intercepteur.
  • Journalise toutes les exceptions. L’attente de la continuation permet d’ajouter la logique après l’exécution de la méthode de service. En attendant la continuation dans un bloc try-catch, les erreurs des méthodes peuvent être consignées.

La signature des méthodes des intercepteurs clients et de serveur est similaire :

  • continuation signifie délégué pour un RPC entrant appelant l’intercepteur suivant dans la chaîne ou le gestionnaire de service (s’il n’y a plus d’intercepteur dans la chaîne). À l’instar des intercepteurs clients, vous pouvez l’appeler à tout moment, et il n’est pas nécessaire de retourner une réponse directement à partir du délégué de continuation. Une logique sortante peut être ajoutée après l’exécution d’un gestionnaire de service en attendant la continuation.
  • context transporte les métadonnées associées à l’appel côté serveur, comme les métadonnées de requête, les échéances et l’annulation, ou le résultat RPC.

Pour plus d’informations sur la création d’un intercepteur de serveur, consultez l’exemple ServerLoggerInterceptor.cs dans le dépôt GitHub grpc/grpc-dotnet.

Configurer des intercepteurs de serveur

Les intercepteurs de serveur gRPC sont configurés au démarrage. Le code suivant :

  • Ajoute gRPC à l’application avec AddGrpc.
  • Configure ServerLoggerInterceptor pour tous les services en l’ajoutant à la collection de Interceptors de l’option de service.
public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc(options =>
    {
        options.Interceptors.Add<ServerLoggerInterceptor>();
    });
}

Un intercepteur peut également être configuré pour un service spécifique en utilisant AddServiceOptions et en spécifiant le type de service.

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

Les intercepteurs sont exécutés dans l’ordre dans lequel ils sont ajoutés à InterceptorCollection. Si les intercepteurs de service global et unique sont configurés, les intercepteurs globalement configurés sont exécutés avant ceux configurés pour un seul service.

Par défaut, les intercepteurs de serveur gRPC ont une durée de vie par requête. Le remplacement de ce comportement est possible en inscrivant le type d’intercepteur avec l’injection de dépendances. L’exemple suivant inscrit le ServerLoggerInterceptor avec une durée de vie singleton :

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

    services.AddSingleton<ServerLoggerInterceptor>();
}

Intercepteurs gRPC et intergiciels

L’intergiciel ASP.NET Core offre des fonctionnalités similaires à celles des intercepteurs dans les applications gRPC basées sur C-core. Les intergiciels ASP.NET Core et les intercepteurs sont conceptuellement similaires. Les deux :

  • Ils sont utilisés pour construire un pipeline qui gère une requête gRPC.
  • Permettent au travail d’être effectué avant ou après le composant suivant dans le pipeline.
  • Fournissent l’accès à HttpContext :
    • Dans l’intergiciel, HttpContext est un paramètre.
    • Dans les intercepteurs, HttpContext est accessible à l’aide du paramètre ServerCallContext avec la méthode d’extension ServerCallContext.GetHttpContext. Cette fonctionnalité est spécifique aux intercepteurs qui s’exécutent dans ASP.NET Core.

Différences entre l’intercepteur gRPC et l’intergiciel ASP.NET Core :

  • Les intercepteurs :
    • Utilisent la couche d’abstraction gRPC à l’aide de ServerCallContext.
    • Fournissent l’accès aux éléments suivants :
      • Le message désérialisé envoyé à un appel.
      • Le message retourné par l’appel avant sa sérialisation.
    • Peuvent intercepter et gérer les exceptions levées à partir des services gRPC.
  • Intergiciel (middleware) :
    • S’exécute pour toutes les requêtes HTTP.
    • S’exécute avant les intercepteurs gRPC.
    • Fonctionne sur les messages HTTP/2 sous-jacents.
    • Peut accéder uniquement aux octets des flux de requête et de réponse.

Ressources supplémentaires