Chamar os serviços gRPC com o cliente .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.
Uma biblioteca de clientes gRPC do .NET está disponível no pacote NuGet Grpc.Net.Client. Este documento explica como:
- Configurar um cliente gRPC para chamar os serviços gRPC.
- Fazer chamadas gRPC para métodos unários, de streaming de servidor, de streaming de cliente e de streaming bidirecional.
Configurar o cliente gRPC
Os clientes gRPC são tipos de cliente concretos gerados a partir de arquivos .proto
. O cliente gRPC concreto tem métodos que se traduzem para o serviço gRPC no arquivo .proto
. Por exemplo, um serviço chamado Greeter
gera um tipo GreeterClient
com métodos para chamar o serviço.
Um cliente gRPC é criado a partir de um canal. Comece usando GrpcChannel.ForAddress
para criar um canal e, em seguida, use o canal para criar um cliente gRPC:
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greet.GreeterClient(channel);
Um canal representa uma conexão de longa duração com um serviço gRPC. Quando um canal é criado, ele é configurado com opções relacionadas à chamada de um serviço. Por exemplo, o HttpClient
usado para fazer chamadas, o tamanho máximo da mensagem de envio e recebimento e o registro em log podem ser especificados em GrpcChannelOptions
e usados com GrpcChannel.ForAddress
. Para obter uma lista completa de opções, consulte opções de configuração do cliente.
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var greeterClient = new Greet.GreeterClient(channel);
var counterClient = new Count.CounterClient(channel);
// Use clients to call gRPC services
Configurar TLS
Um cliente gRPC deve usar a mesma segurança em nível de conexão que o serviço chamado. O TLS do cliente gRPC é configurado quando o canal gRPC é criado. Um cliente gRPC lança um erro quando chama um serviço e a segurança em nível de conexão do canal e do serviço não correspondem.
Para configurar um canal gRPC para usar o TLS, verifique se o endereço do servidor começa com https
. Por exemplo, GrpcChannel.ForAddress("https://localhost:5001")
usa o protocolo HTTPS. O canal gRPC negocia automaticamente uma conexão protegida pelo TLS e usa uma conexão segura para fazer chamadas gRPC.
Dica
O gRPC dá suporte à autenticação de certificado do cliente por TLS. Para obter informações sobre como configurar certificados de cliente com um canal gRPC, consulte Autenticação e autorização no gRPC para ASP.NET Core.
Para chamar serviços gRPC não seguros, verifique se o endereço do servidor começa com http
. Por exemplo, GrpcChannel.ForAddress("http://localhost:5000")
usa o protocolo HTTPS. No .NET Core 3.1, é necessária uma configuração adicional para chamar serviços gRPC não seguros com o cliente .NET.
Desempenho de cliente
Desempenho e uso do canal e do cliente:
- A criação de um canal pode ser uma operação cara. A reutilização de um canal para chamadas gRPC oferece benefícios de desempenho.
- Um canal gerencia conexões com o servidor. Se a conexão for fechada ou perdida, o canal se reconectará automaticamente na próxima vez em que uma chamada gRPC for feita.
- Os clientes gRPC são criados com canais. Os clientes gRPC são objetos leves e não precisam ser armazenados em cache ou reutilizados.
- Vários clientes gRPC podem ser criados a partir de um canal, incluindo diferentes tipos de clientes.
- Um canal e clientes criados a partir do canal podem ser usados com segurança por vários threads.
- Os clientes criados a partir do canal podem fazer várias chamadas simultâneas.
GrpcChannel.ForAddress
não é a única opção para criar um cliente gRPC. Se chamar serviços gRPC de um aplicativo ASP.NET Core, considere a integração de fábrica do cliente gRPC. A integração do gRPC com HttpClientFactory
oferece uma alternativa centralizada para a criação de clientes gRPC.
Observação
No momento, não há suporte para chamar gRPC por HTTP/2 com Grpc.Net.Client
no Xamarin. Estamos trabalhando para melhorar o suporte a HTTP/2 em uma versão futura do Xamarin. Grpc.Core e gRPC-Web são alternativas viáveis que funcionam hoje.
Fazer chamadas gRPC
Uma chamada gRPC é iniciada chamando um método no cliente. O cliente gRPC manipulará a serialização de mensagens e abordará a chamada gRPC para o serviço correto.
O gRPC tem diferentes tipos de métodos. Como o cliente é usado para fazer uma chamada gRPC depende do tipo de método chamado. Os tipos de método gRPC são:
- Unário
- Streaming de servidor
- Streaming de cliente
- Streaming bidirecional
Chamada unária
Uma chamada unária começa com o cliente enviando uma mensagem de solicitação. Uma mensagem de resposta é retornada quando o serviço é concluído.
var client = new Greet.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });
Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello World
Cada método de serviço unário no arquivo .proto
resultará em dois métodos .NET no tipo de cliente gRPC concreto para chamar o método: um método assíncrono e um método de bloqueio. Por exemplo, em GreeterClient
, há duas maneiras de chamar SayHello
:
GreeterClient.SayHelloAsync
– chama o serviçoGreeter.SayHello
de forma assíncrona. Pode ser aguardado.GreeterClient.SayHello
– chama o serviçoGreeter.SayHello
e bloqueia até a conclusão. Não use em código assíncrono.
Chamada de streaming de servidor
Uma chamada de streaming de servidor começa com o cliente enviando uma mensagem de solicitação. ResponseStream.MoveNext()
lê mensagens transmitidas do serviço. A chamada de streaming de servidor é concluída quando ResponseStream.MoveNext()
retorna false
.
var client = new Greet.GreeterClient(channel);
using var call = client.SayHellos(new HelloRequest { Name = "World" });
while (await call.ResponseStream.MoveNext())
{
Console.WriteLine("Greeting: " + call.ResponseStream.Current.Message);
// "Greeting: Hello World" is written multiple times
}
Ao usar o C# 8 ou posterior, a sintaxe await foreach
pode ser usada para ler mensagens. O método de extensão IAsyncStreamReader<T>.ReadAllAsync()
lê todas as mensagens do fluxo de resposta:
var client = new Greet.GreeterClient(channel);
using var call = client.SayHellos(new HelloRequest { Name = "World" });
await foreach (var response in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine("Greeting: " + response.Message);
// "Greeting: Hello World" is written multiple times
}
O tipo retornado do início de uma chamada de streaming do servidor implementa IDisposable
. Sempre descarte uma chamada de streaming para garantir que ela seja interrompida e que todos os recursos sejam limpos.
Chamada de streaming de cliente
Uma chamada de streaming de cliente é iniciada sem que o cliente envie uma mensagem. O cliente pode optar por enviar mensagens com RequestStream.WriteAsync
. Quando o cliente termina de enviar mensagens, RequestStream.CompleteAsync()
deverá ser chamado para notificar o serviço. A chamada é concluída quando o serviço retorna uma mensagem de resposta.
var client = new Counter.CounterClient(channel);
using var call = client.AccumulateCount();
for (var i = 0; i < 3; i++)
{
await call.RequestStream.WriteAsync(new CounterRequest { Count = 1 });
}
await call.RequestStream.CompleteAsync();
var response = await call;
Console.WriteLine($"Count: {response.Count}");
// Count: 3
O tipo retornado do início de uma chamada de streaming do cliente implementa IDisposable
. Sempre descarte uma chamada de streaming para garantir que ela seja interrompida e que todos os recursos sejam limpos.
Chamada de streaming bidirecional
Uma chamada de streaming bidirecional é iniciada sem que o cliente envie uma mensagem. O cliente pode optar por enviar mensagens com RequestStream.WriteAsync
. As mensagens transmitidas do serviço são acessíveis com ResponseStream.MoveNext()
ou ResponseStream.ReadAllAsync()
. A chamada de streaming bidirecional é concluída quando o ResponseStream
não tem mais mensagens.
var client = new Echo.EchoClient(channel);
using var call = client.Echo();
Console.WriteLine("Starting background task to receive messages");
var readTask = Task.Run(async () =>
{
await foreach (var response in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine(response.Message);
// Echo messages sent to the service
}
});
Console.WriteLine("Starting to send messages");
Console.WriteLine("Type a message to echo then press enter.");
while (true)
{
var result = Console.ReadLine();
if (string.IsNullOrEmpty(result))
{
break;
}
await call.RequestStream.WriteAsync(new EchoMessage { Message = result });
}
Console.WriteLine("Disconnecting");
await call.RequestStream.CompleteAsync();
await readTask;
Para obter o melhor desempenho e evitar erros desnecessários no cliente e no serviço, tente concluir chamadas de streaming bidirecionais normalmente. Uma chamada bidirecional é concluída normalmente quando o servidor termina de ler o fluxo de solicitação e o cliente termina de ler o fluxo de resposta. A chamada de exemplo anterior é um exemplo de uma chamada bidirecional que termina normalmente. Na chamada, o cliente:
- Inicia uma nova chamada de streaming bidirecional chamando
EchoClient.Echo
. - Cria uma tarefa em segundo plano para ler mensagens do serviço usando
ResponseStream.ReadAllAsync()
. - Envia mensagens para o servidor com
RequestStream.WriteAsync
. - Notifica o servidor de que terminou de enviar mensagens com
RequestStream.CompleteAsync()
. - Aguarda até que a tarefa em segundo plano tenha lido todas as mensagens de entrada.
Durante uma chamada de streaming bidirecional, o cliente e o serviço podem enviar mensagens entre si a qualquer momento. A melhor lógica de cliente para interagir com uma chamada bidirecional varia dependendo da lógica de serviço.
O tipo retornado do início de uma chamada de streaming bidirecional implementa IDisposable
. Sempre descarte uma chamada de streaming para garantir que ela seja interrompida e que todos os recursos sejam limpos.
Acessar cabeçalhos gRPC
As chamadas gRPC retornam cabeçalhos de resposta. Os cabeçalhos de resposta HTTP passam metadados de nome/valor sobre uma chamada que não está relacionada à mensagem retornada.
Os cabeçalhos são acessíveis usando ResponseHeadersAsync
, que retorna uma coleção de metadados. Os cabeçalhos normalmente são retornados com a mensagem de resposta; portanto, você deve aguardá-los.
var client = new Greet.GreeterClient(channel);
using var call = client.SayHelloAsync(new HelloRequest { Name = "World" });
var headers = await call.ResponseHeadersAsync;
var myValue = headers.GetValue("my-trailer-name");
var response = await call.ResponseAsync;
Uso de ResponseHeadersAsync
:
- Deve aguardar o resultado de
ResponseHeadersAsync
para obter a coleção de cabeçalhos. - Não precisa ser acessado antes de
ResponseAsync
(ou o fluxo de resposta ao transmitir por streaming). Se uma resposta tiver sido retornada,ResponseHeadersAsync
retornará cabeçalhos instantaneamente. - Gerará uma exceção se houver um erro de conexão ou servidor e os cabeçalhos não forem retornados para a chamada gRPC.
Acessar trailers gRPC
As chamadas gRPC podem retornar trailers de resposta. Os trailers são usados para fornecer metadados de nome/valor sobre uma chamada. Os trailers fornecem funcionalidade semelhante aos cabeçalhos HTTP, mas são recebidos no final da chamada.
Os cabeçalhos são acessíveis usando GetTrailers()
, que retorna uma coleção de metadados. Os trailers são retornados após a conclusão da resposta. Portanto, você deve aguardar todas as mensagens de resposta antes de acessar os trailers.
As chamadas de streaming unárias e de cliente devem aguardar ResponseAsync
antes de chamar GetTrailers()
:
var client = new Greet.GreeterClient(channel);
using var call = client.SayHelloAsync(new HelloRequest { Name = "World" });
var response = await call.ResponseAsync;
Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello World
var trailers = call.GetTrailers();
var myValue = trailers.GetValue("my-trailer-name");
As chamadas de streaming de servidor e bidirecional devem terminar de aguardar o fluxo de resposta antes de chamar GetTrailers()
:
var client = new Greet.GreeterClient(channel);
using var call = client.SayHellos(new HelloRequest { Name = "World" });
await foreach (var response in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine("Greeting: " + response.Message);
// "Greeting: Hello World" is written multiple times
}
var trailers = call.GetTrailers();
var myValue = trailers.GetValue("my-trailer-name");
Os trailers também podem ser acessados por meio de RpcException
. Um serviço pode retornar trailers junto com uma status gRPC diferente de OK. Nessa situação, os trailers são recuperados da exceção gerada pelo cliente gRPC:
var client = new Greet.GreeterClient(channel);
string myValue = null;
try
{
using var call = client.SayHelloAsync(new HelloRequest { Name = "World" });
var response = await call.ResponseAsync;
Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello World
var trailers = call.GetTrailers();
myValue = trailers.GetValue("my-trailer-name");
}
catch (RpcException ex)
{
var trailers = ex.Trailers;
myValue = trailers.GetValue("my-trailer-name");
}
Configurar prazo
É recomendável configurar um prazo de chamada gRPC porque ele fornece o tempo máximo de execução de uma chamada. Ela impede que serviços funcionando de forma inadequada sejam executados para sempre e o esgotamento dos recursos do servidor. Os prazos são uma ferramenta útil para compilar aplicativos confiáveis.
Configure CallOptions.Deadline
para definir um prazo para uma chamada gRPC:
var client = new Greet.GreeterClient(channel);
try
{
var response = await client.SayHelloAsync(
new HelloRequest { Name = "World" },
deadline: DateTime.UtcNow.AddSeconds(5));
// Greeting: Hello World
Console.WriteLine("Greeting: " + response.Message);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.DeadlineExceeded)
{
Console.WriteLine("Greeting timeout.");
}
Para obter mais informações, consulte Serviços gRPC confiáveis com prazos finais e cancelamento.