Interceptores de gRPC en .NET
Nota:
Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión .NET 8 de este artículo.
Advertencia
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulte la versión .NET 8 de este artículo.
Importante
Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.
Para la versión actual, consulte la versión .NET 8 de este artículo.
Por Ernest Nguyen
Los interceptores son un concepto de gRPC que permite a las aplicaciones interactuar con llamadas gRPC entrantes o salientes. Ofrecen una manera de enriquecer la canalización de procesamiento de solicitudes.
Los interceptores se configuran para un canal o servicio, y se ejecutan automáticamente para cada llamada a gRPC. Como los interceptores son transparentes para la lógica de aplicación del usuario, son una excelente solución para casos comunes, como los de registro, supervisión, autenticación y validación.
Tipo de Interceptor
Los interceptores se pueden implementar para servidores y clientes gRPC mediante la creación de una clase que hereda del tipo Interceptor
:
public class ExampleInterceptor : Interceptor
{
}
De forma predeterminada, la clase base Interceptor
no hace nada. Para agregar comportamiento a un interceptor, invalide los métodos de clase base adecuados en una implementación de interceptor.
Interceptores de cliente
Los interceptores de cliente gRPC interceptan las invocaciones de RPC salientes. Proporcionan acceso a la solicitud enviada, la respuesta entrante y el contexto de una llamada del lado cliente.
Métodos Interceptor
que se invalidarán para el cliente:
BlockingUnaryCall
: intercepta una invocación de bloqueo de una RPC unaria.AsyncUnaryCall
: intercepta una invocación asincrónica de una RPC unaria.AsyncClientStreamingCall
: intercepta una invocación asincrónica de una RPC de streaming de cliente.AsyncServerStreamingCall
: intercepta una invocación asincrónica de una RPC de streaming de servidor.AsyncDuplexStreamingCall
: intercepta una invocación asincrónica de una RPC de streaming bidireccional.
Advertencia
Aunque tanto BlockingUnaryCall
como AsyncUnaryCall
hacen referencia a RPC unarias, no son intercambiables. Una invocación de bloqueo no es interceptada por AsyncUnaryCall
y una invocación asincrónica no es interceptada por BlockingUnaryCall
.
Creación de un interceptor gRPC de cliente
En el código siguiente se presenta un ejemplo básico de interceptación de una invocación asincrónica de una llamada 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);
}
}
El reemplazo de AsyncUnaryCall
:
- Intercepta una llamada unaria asincrónica.
- Registra detalles sobre la llamada.
- Llama al parámetro
continuation
pasado al método. Esto invoca el siguiente interceptor de la cadena o el invocador de llamada subyacente si este es el último interceptor.
Los métodos de Interceptor
para cada tipo de método de servicio tienen signaturas diferentes. Pero el concepto subyacente a los parámetros continuation
y context
sigue siendo el mismo:
continuation
es un delegado que invoca el siguiente interceptor en la cadena o el invocador de llamada subyacente (si no queda ningún interceptor en la cadena). No es un error llamarlo cero o varias veces. No es necesario que los interceptores devuelvan una representación de llamada (AsyncUnaryCall
en caso de RPC unaria) devuelta desde el delegadocontinuation
. Si se omite la llamada de delegado y se devuelve una instancia de representación de llamada propia, se interrumpe la cadena de los interceptores y se devuelve la respuesta asociada inmediatamente.context
lleva valores con ámbito asociados a la llamada del lado cliente. Usecontext
para pasar metadatos, como entidades de seguridad, credenciales o datos de seguimiento. Además,context
contiene información sobre las fechas límite y la cancelación. Para más información, consulte Servicios gRPC confiables con fechas límite y cancelación.
Espera de respuesta en el interceptor de cliente
Un interceptor puede esperar la respuesta en llamadas de streaming unarias y de cliente mediante la actualización del valor AsyncUnaryCall<TResponse>.ResponseAsync
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);
}
}
}
El código anterior:
- Crea un interceptor que invalida
AsyncUnaryCall
. - El reemplazo de
AsyncUnaryCall
:- Llama al parámetro
continuation
para invocar el siguiente elemento de la cadena del interceptor. - Crea una instancia de
AsyncUnaryCall<TResponse>
basada en el resultado de la continuación. - Encapsula la tarea
ResponseAsync
mediante el métodoHandleResponse
. - Espera la respuesta con
HandleResponse
. La espera de la respuesta permite agregar lógica después de que el cliente haya recibido la respuesta. Al esperar la respuesta en un bloque try-catch, se pueden registrar los errores de las llamadas.
- Llama al parámetro
Para más información sobre cómo crear un interceptor de cliente, vea el ejemplo ClientLoggerInterceptor.cs
en el repositorio de grpc/grpc-dotnet
GitHub.
Configuración de interceptores de cliente
Los interceptores de cliente gRPC se configuran en un canal.
El código siguiente:
- Crea un canal mediante
GrpcChannel.ForAddress
. - Usa el método de extensión
Intercept
a fin de configurar el canal para usar el interceptor. Observe que este método devuelve un objetoCallInvoker
. Los clientes gRPC fuertemente tipados se pueden crear a partir de un invocador como un canal. - Crea un cliente a partir del invocador. Las llamadas gRPC realizadas por el cliente ejecutan automáticamente el interceptor.
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var invoker = channel.Intercept(new ClientLoggerInterceptor());
var client = new Greeter.GreeterClient(invoker);
El método de extensión Intercept
se puede encadenar para configurar varios interceptores para un canal. Como alternativa, hay una sobrecarga de Intercept
que acepta varios interceptores. Se puede ejecutar cualquier número de interceptores para una sola llamada gRPC, como se muestra en el ejemplo siguiente:
var invoker = channel
.Intercept(new ClientTokenInterceptor())
.Intercept(new ClientMonitoringInterceptor())
.Intercept(new ClientLoggerInterceptor());
Los interceptores se invocan en orden inverso de los métodos de extensión Intercept
encadenados. En el código anterior, los interceptores se invocan en el orden siguiente:
ClientLoggerInterceptor
ClientMonitoringInterceptor
ClientTokenInterceptor
Para obtener información sobre cómo configurar interceptores con el generador de cliente gRPC, vea Integración de la fábrica de cliente gRPC en .NET.
Interceptores de servidor
Los interceptores de servidor gRPC interceptan las solicitudes de RPC entrantes. Proporcionan acceso a la solicitud entrante, la respuesta saliente y el contexto de una llamada del lado servidor.
Métodos Interceptor
que se invalidarán para el servidor:
UnaryServerHandler
: intercepta una RPC unaria.ClientStreamingServerHandler
: intercepta una RPC de streaming de cliente.ServerStreamingServerHandler
: intercepta una RPC de streaming de servidor.DuplexStreamingServerHandler
: intercepta una RPC de streaming bidireccional.
Creación de un interceptor gRPC de servidor
En el código siguiente se presenta un ejemplo de una interceptación de una RPC unaria entrante:
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;
}
}
}
El reemplazo de UnaryServerHandler
:
- Intercepta una llamada unaria entrante.
- Registra detalles sobre la llamada.
- Llama al parámetro
continuation
pasado al método. Esto invoca el siguiente interceptor de la cadena o el controlador de servicio si este es el último interceptor. - Registra las excepciones. La espera de la continuación permite agregar lógica después de que se haya ejecutado el método de servicio. Al esperar la continuación en un bloque try-catch, se pueden registrar los errores de los métodos.
La signatura de los métodos interceptores de cliente y servidor es similar:
continuation
equivale a un delegado para una RPC entrante que llama al siguiente interceptor de la cadena o al controlador de servicio (si no queda ningún interceptor en la cadena). De forma similar a los interceptores de cliente, puede llamarlo en cualquier momento y no es necesario devolver una respuesta directamente desde el delegado de continuación. La lógica de salida se puede agregar después de que se haya ejecutado un controlador de servicio esperando la continuación.context
lleva metadatos asociados a la llamada del lado servidor, como metadatos de solicitud, fechas límite y cancelación, o resultado de RPC.
Para más información sobre cómo crear un interceptor de servidor, vea el ejemplo ServerLoggerInterceptor.cs
en el repositorio de grpc/grpc-dotnet
GitHub.
Configuración de interceptores de servidor
Los interceptores de servidor gRPC se configuran durante el inicio. El código siguiente:
- Agrega gRPC a la aplicación con
AddGrpc
. - Configura
ServerLoggerInterceptor
para todos los servicios agregándole a la colecciónInterceptors
de la opción de servicio.
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc(options =>
{
options.Interceptors.Add<ServerLoggerInterceptor>();
});
}
Un interceptor también se puede configurar para un servicio específico mediante AddServiceOptions
y la especificación del tipo de servicio.
public void ConfigureServices(IServiceCollection services)
{
services
.AddGrpc()
.AddServiceOptions<GreeterService>(options =>
{
options.Interceptors.Add<ServerLoggerInterceptor>();
});
}
Los interceptores se ejecutan en el orden en que se agregan a InterceptorCollection
. Si se configuran interceptores de servicio únicos y globales, los interceptores configurados globalmente se ejecutan antes que los configurados para un único servicio.
De manera predeterminada, los interceptores de servicio de gRPC tienen una duración por solicitud. La invalidación de este comportamiento es posible mediante el registro del tipo de interceptor con inserción de dependencias. En el ejemplo siguiente se registra ServerLoggerInterceptor
con una duración singleton:
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc(options =>
{
options.Interceptors.Add<ServerLoggerInterceptor>();
});
services.AddSingleton<ServerLoggerInterceptor>();
}
Interceptores de gRPC frente a middleware
El middleware de ASP.NET Core ofrece funcionalidades similares a los interceptores de aplicaciones gRPC basadas en C-core. El middleware y los interceptores de ASP.NET Core son conceptualmente similares. Ambos:
- Se usan para construir una canalización que controla una solicitud gRPC.
- Permiten que se realicen tareas antes o después del siguiente componente de la canalización.
- Proporcionan acceso a
HttpContext
:- En el middleware,
HttpContext
es un parámetro. - En los interceptores, se puede acceder a
HttpContext
mediante el parámetroServerCallContext
con el método de extensiónServerCallContext.GetHttpContext
. Esta característica es específica de los interceptores que se ejecutan en ASP.NET Core.
- En el middleware,
Diferencias entre los interceptores de gRPC y el middleware de ASP.NET Core:
- Interceptores:
- Operan en el nivel de abstracción de gRPC mediante la clase
ServerCallContext
. - Proporcionan acceso a los elementos siguientes:
- Al mensaje deserializado que se envía a una llamada.
- Al mensaje que se devuelve de la llamada antes de que se serialice.
- Pueden detectar y controlar las excepciones que se producen en los servicios gRPC.
- Operan en el nivel de abstracción de gRPC mediante la clase
- Middleware:
- Se ejecuta para todas las solicitudes HTTP.
- Se ejecuta antes que los interceptores de gRPC.
- Opera en los mensajes HTTP/2 subyacentes.
- Solo puede acceder a los bytes de las secuencias de solicitud y respuesta.