interceptadores gRPC no .NET
Observação
Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, confira .NET e a Política de Suporte do .NET Core. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Importante
Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Por Ernest Nguyen
Interceptadores são um conceito de gRPC que permite que os aplicativos interajam com chamadas gRPC de entrada ou saída. Eles oferecem uma maneira de enriquecer o pipeline de processamento de solicitação.
Os interceptadores são configurados para um canal ou serviço e executados automaticamente para cada chamada gRPC. Como os interceptadores são transparentes para a lógica do aplicativo do usuário, eles são uma excelente solução para casos comuns, como registro em log, monitoramento, autenticação e validação.
TipoInterceptor
Os interceptadores podem ser implementados para servidores gRPC e clientes criando uma classe que herda do tipo Interceptor
:
public class ExampleInterceptor : Interceptor
{
}
Por padrão, a classe base Interceptor
não faz nada. Adicione comportamento a um interceptador substituindo os métodos de classe base apropriados em uma implementação de interceptador.
Interceptadores de cliente
Interceptadores de cliente gRPC interceptam invocações RPC de saída. Eles fornecem acesso à solicitação enviada, à resposta de entrada e ao contexto de uma chamada do lado do cliente.
Métodos Interceptor
a serem substituídos pelo cliente:
BlockingUnaryCall
: intercepta uma invocação de bloqueio de um RPC unário.AsyncUnaryCall
: intercepta uma invocação assíncrona de um RPC unário.AsyncClientStreamingCall
: intercepta uma invocação assíncrona de um RPC de fluxo de dados de cliente.AsyncServerStreamingCall
: intercepta uma invocação assíncrona de um RPC de fluxo de dados de servidor.AsyncDuplexStreamingCall
: intercepta uma invocação assíncrona de um RPC de fluxo de dados bidirecional.
Aviso
Embora ambos BlockingUnaryCall
e AsyncUnaryCall
se refiram a RPCs unários, eles não são intercambiáveis. Uma invocação de bloqueio não é interceptada por AsyncUnaryCall
e uma invocação assíncrona não é interceptada por um BlockingUnaryCall
.
Criar um interceptador gRPC do cliente
O código a seguir apresenta um exemplo básico de interceptação de uma invocação assíncrona de uma chamada unária:
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);
}
}
Substituindo AsyncUnaryCall
:
- Intercepta uma chamada unária assíncrona.
- Registra detalhes sobre a chamada.
- Chama o parâmetro
continuation
passado para o método. Isso invoca o próximo interceptador na cadeia ou no invocador de chamadas subjacente se este for o último interceptador.
Os métodos em Interceptor
para cada tipo de método de serviço têm assinaturas diferentes. No entanto, o conceito por trás dos parâmetros continuation
e context
permanece o mesmo:
continuation
é um delegado que invoca o próximo interceptador na cadeia ou o invocador de chamada subjacente (se não houver nenhum interceptador na cadeia). Não é um erro chamá-lo de zero ou várias vezes. Interceptadores não são necessários para retornar uma representação de chamada (AsyncUnaryCall
no caso de RPC unário) retornada do delegadocontinuation
. Omitir a chamada delegada e retornar sua própria instância de representação de chamada interrompe a cadeia de interceptadores e retorna a resposta associada imediatamente.context
carrega valores com escopo associados à chamada do lado do cliente. Usecontext
para passar metadados, como entidades de segurança, credenciais ou dados de rastreamento. Além disso,context
traz informações sobre prazos e cancelamento. Para obter mais informações, confira Serviços gRPC confiáveis com prazos finais e cancelamento.
Aguardando resposta no interceptador de cliente
Um interceptador pode aguardar a resposta em chamadas de fluxo de dados unárias e de cliente atualizando o valor 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);
}
}
}
O código anterior:
- Cria um novo interceptador que substitui
AsyncUnaryCall
. - Substituindo
AsyncUnaryCall
:- Chama o parâmetro
continuation
para invocar o próximo item na cadeia de interceptadores. - Cria uma nova instância
AsyncUnaryCall<TResponse>
com base no resultado da continuação. - Encapsula a tarefa
ResponseAsync
usando o métodoHandleResponse
. - Aguarda a resposta com
HandleResponse
. Aguardar a resposta permite que a lógica seja adicionada depois que o cliente recebeu a resposta. Ao aguardar a resposta em um bloco try-catch, erros de chamadas podem ser registrados em log.
- Chama o parâmetro
Para obter mais informações sobre como criar um interceptador de cliente, confira o ClientLoggerInterceptor.cs
exemplo no grpc/grpc-dotnet
repositório GitHub.
Configurar interceptadores de cliente
Os interceptadores de cliente gRPC são configurados em um canal.
O seguinte código:
- Cria um canal usando
GrpcChannel.ForAddress
. - Usa o método de extensão
Intercept
para configurar o canal para usar o interceptador. Observe que esse método retorna umCallInvoker
. Clientes gRPC fortemente tipados podem ser criados a partir de um invocador, assim como um canal. - Cria um cliente no invocador. As chamadas gRPC feitas pelo cliente executam automaticamente o interceptador.
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var invoker = channel.Intercept(new ClientLoggerInterceptor());
var client = new Greeter.GreeterClient(invoker);
O método de extensão Intercept
pode ser encadeado para configurar vários interceptadores para um canal. Como alternativa, há uma sobrecarga de Intercept
que aceita vários interceptadores. Qualquer número de interceptadores pode ser executado para uma única chamada gRPC, como demonstra o exemplo a seguir:
var invoker = channel
.Intercept(new ClientTokenInterceptor())
.Intercept(new ClientMonitoringInterceptor())
.Intercept(new ClientLoggerInterceptor());
Os interceptadores são invocados na ordem inversa dos métodos de extensão Intercept
encadeados. No código anterior, os interceptadores são invocados na seguinte ordem:
ClientLoggerInterceptor
ClientMonitoringInterceptor
ClientTokenInterceptor
Para obter informações sobre como configurar interceptadores com a fábrica de clientes gRPC, confira Integração de fábrica do cliente gRPC no .NET.
Interceptadores de servidor
Os interceptadores de servidor gRPC interceptam solicitações RPC de entrada. Eles fornecem acesso à solicitação de entrada, à resposta de saída e ao contexto de uma chamada do lado do servidor.
Métodos Interceptor
a serem substituídos pelo cliente:
UnaryServerHandler
: intercepta um RPC unário.ClientStreamingServerHandler
: intercepta um RPC de fluxo de dados de cliente.ServerStreamingServerHandler
: intercepta um RPC de fluxo de dados de servidor.DuplexStreamingServerHandler
: intercepta um RPC de fluxo de dados bidirecional.
Criar um interceptador gRPC do servidor
O código a seguir apresenta um exemplo de uma interceptação de um RPC unário de entrada:
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;
}
}
}
Substituindo UnaryServerHandler
:
- Intercepta uma chamada unária de entrada.
- Registra detalhes sobre a chamada.
- Chama o parâmetro
continuation
passado para o método. Isso invoca o próximo interceptador na cadeia ou no invocador de chamadas subjacente se este for o último interceptador. - Registra todas as exceções. Aguardar a continuação permite que a lógica seja adicionada após a execução do método de serviço. Ao aguardar a continuação em um bloco try-catch, os erros de métodos podem ser registrados em log.
A assinatura de métodos de interceptadores de cliente e servidor são semelhantes:
continuation
significa um delegado para um RPC de entrada que chama o próximo interceptador na cadeia ou no manipulador de serviço (se não houver nenhum interceptador na cadeia). Semelhante aos interceptadores de cliente, você pode chamá-lo a qualquer momento e não há necessidade de retornar uma resposta diretamente do delegado de continuação. A lógica de saída pode ser adicionada depois que um manipulador de serviço for executado aguardando a continuação.context
carrega metadados associados à chamada do lado do servidor, como metadados de solicitação, prazos e cancelamento ou resultado de RPC.
Para obter mais informações sobre como criar um interceptador de servidor, veja o exemplo de ServerLoggerInterceptor.cs
no do repositório GitHubgrpc/grpc-dotnet
.
Configurar interceptadores de servidor
Os interceptadores de servidor gRPC são configurados na inicialização. O seguinte código:
- Adiciona gRPC ao aplicativo com
AddGrpc
. - Configura
ServerLoggerInterceptor
para todos os serviços adicionando-o à coleção da opçãoInterceptors
de serviço.
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc(options =>
{
options.Interceptors.Add<ServerLoggerInterceptor>();
});
}
Um interceptador também pode ser configurado para um serviço específico usando AddServiceOptions
e especificando o tipo de serviço.
public void ConfigureServices(IServiceCollection services)
{
services
.AddGrpc()
.AddServiceOptions<GreeterService>(options =>
{
options.Interceptors.Add<ServerLoggerInterceptor>();
});
}
Os interceptadores são executados na ordem em que são adicionados ao InterceptorCollection
. Se os interceptadores globais e de serviço único estiverem configurados, os interceptadores configurados globalmente serão executados antes daqueles configurados para um único serviço.
Por padrão, os interceptadores de servidor gRPC têm um tempo de vida por solicitação. Substituir esse comportamento é possível por meio do registro do tipo de interceptador com injeção de dependência. O exemplo a seguir registra o ServerLoggerInterceptor
com um tempo de vida singleton:
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc(options =>
{
options.Interceptors.Add<ServerLoggerInterceptor>();
});
services.AddSingleton<ServerLoggerInterceptor>();
}
Interceptadores gRPC versus Middleware
O middleware do ASP.NET Core oferece funcionalidades semelhantes em comparação com interceptadores em aplicativos gRPC baseados em C-core. O middleware e interceptadores do ASP.NET Core são conceitualmente semelhantes. Ambos:
- Eles são usados para construir um pipeline que manipula uma solicitação gRPC.
- Eles permitem que o trabalho seja executado antes ou depois do próximo componente no pipeline.
- Forneça acesso ao
HttpContext
:- No middleware, o
HttpContext
é um parâmetro. - Nos interceptadores, o
HttpContext
pode ser acessado usando o parâmetroServerCallContext
com o método de extensãoServerCallContext.GetHttpContext
. Esse recurso é específico para interceptadores em execução no ASP.NET Core.
- No middleware, o
Diferenças do interceptador gRPC no middleware ASP.NET Core:
- Interceptadores:
- Opere na camada gRPC de abstração usando o
ServerCallContext
. - Forneça acesso:
- À mensagem desserializada enviada a uma chamada.
- À mensagem retornada da chamada antes de ser serializada.
- É possível capturar e manipular exceções geradas nos serviços gRPC.
- Opere na camada gRPC de abstração usando o
- Middleware:
- É executado para todas as solicitações HTTP.
- É executado antes dos interceptadores gRPC.
- Opera nas mensagens HTTP/2 subjacentes.
- Só é possível acessar bytes nos fluxos de solicitação e resposta.