Chiamare servizi gRPC con il client .NET

Una libreria client .NET gRPC è disponibile nel pacchetto NuGet Grpc.Net.Client . Questo documento illustra come:

  • Configurare un client gRPC per chiamare i servizi gRPC.
  • Effettuare chiamate gRPC a metodi unari, streaming server, streaming client e streaming bidirezionale.

Configurare il client gRPC

I client gRPC sono tipi client concreti generati da file .proto. Il client gRPC concreto include metodi che si traducono nel servizio gRPC nel file .proto. Ad esempio, un servizio denominato Greeter genera un GreeterClient tipo con metodi per chiamare il servizio.

Un client gRPC viene creato da un canale. Iniziare usando GrpcChannel.ForAddress per creare un canale e quindi usare il canale per creare un client gRPC:

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greet.GreeterClient(channel);

Un canale rappresenta una connessione di lunga durata a un servizio gRPC. Quando viene creato un canale, viene configurato con le opzioni correlate alla chiamata di un servizio. Ad esempio, l'oggetto HttpClient usato per effettuare chiamate, le dimensioni massime di invio e ricezione dei messaggi e la registrazione possono essere specificate GrpcChannelOptions e usate con GrpcChannel.ForAddress. Per un elenco completo delle opzioni, vedere Opzioni di configurazione client.

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

Configurare TLS

Un client gRPC deve usare la stessa sicurezza a livello di connessione del servizio chiamato. Il client gRPC Transport Layer Security (TLS) viene configurato quando viene creato il canale gRPC. Un client gRPC genera un errore quando chiama un servizio e la sicurezza a livello di connessione del canale e del servizio non corrispondono.

Per configurare un canale gRPC per l'uso di TLS, assicurarsi che l'indirizzo del server inizi con https. Ad esempio, GrpcChannel.ForAddress("https://localhost:5001") usa il protocollo HTTPS. Il canale gRPC negozia automaticamente una connessione protetta da TLS e usa una connessione sicura per effettuare chiamate gRPC.

Suggerimento

gRPC supporta l'autenticazione del certificato client tramite TLS. Per informazioni sulla configurazione dei certificati client con un canale gRPC, vedere Autenticazione e autorizzazione in gRPC per ASP.NET Core.

Per chiamare i servizi gRPC non protetti, verificare che l'indirizzo del server inizi con http. Ad esempio, GrpcChannel.ForAddress("http://localhost:5000") usa il protocollo HTTP. In .NET Core 3.1 è necessaria una configurazione aggiuntiva per chiamare servizi gRPC non sicuri con il client .NET.

Prestazioni client

Prestazioni e utilizzo di canale e client:

  • La creazione di un canale può essere un'operazione costosa. Il riutilizzo di un canale per le chiamate gRPC offre vantaggi in termini di prestazioni.
  • Un canale gestisce le connessioni al server. Se la connessione viene chiusa o persa, il canale si riconnette automaticamente alla successiva chiamata gRPC.
  • I client gRPC vengono creati con i canali. I client gRPC sono oggetti leggeri e non devono essere memorizzati nella cache o riutilizzati.
  • È possibile creare più client gRPC da un canale, inclusi diversi tipi di client.
  • Un canale e i client creati dal canale possono essere usati in modo sicuro da più thread.
  • I client creati dal canale possono effettuare più chiamate simultanee.

GrpcChannel.ForAddress non è l'unica opzione per la creazione di un client gRPC. Se si chiamano i servizi gRPC da un'app ASP.NET Core, prendere in considerazione l'integrazione della factory client gRPC. L'integrazione di gRPC con HttpClientFactory offre un'alternativa centralizzata alla creazione di client gRPC.

Nota

La chiamata di gRPC su HTTP/2 con Grpc.Net.Client non è attualmente supportata in Xamarin. Microsoft sta lavorando per migliorare il supporto HTTP/2 in una versione futura di Xamarin. Grpc.Core e gRPC-Web sono alternative valide che funzionano oggi.

Effettuare chiamate gRPC

Una chiamata gRPC viene avviata chiamando un metodo sul client. Il client gRPC gestirà la serializzazione dei messaggi e indirizza la chiamata gRPC al servizio corretto.

gRPC ha diversi tipi di metodi. Il modo in cui il client viene usato per effettuare una chiamata gRPC dipende dal tipo di metodo chiamato. I tipi di metodo gRPC sono:

  • Unario
  • Streaming del server
  • Streaming client
  • Streaming bidirezionale

Chiamata unaria

Una chiamata unaria inizia con il client che invia un messaggio di richiesta. Al termine del servizio viene restituito un messaggio di risposta.

var client = new Greet.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });

Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello World

Ogni metodo di servizio unario nel .proto file genererà due metodi .NET sul tipo di client gRPC concreto per chiamare il metodo: un metodo asincrono e un metodo di blocco. Ad esempio, in GreeterClient esistono due modi per chiamare SayHello:

  • GreeterClient.SayHelloAsync : chiama Greeter.SayHello il servizio in modo asincrono. Può essere atteso.
  • GreeterClient.SayHello : chiama Greeter.SayHello il servizio e blocca fino al completamento. Non usare nel codice asincrono.

Chiamata di streaming del server

Una chiamata di streaming del server inizia con il client che invia un messaggio di richiesta. ResponseStream.MoveNext() legge i messaggi trasmessi dal servizio. La chiamata di streaming del server è completa quando ResponseStream.MoveNext() restituisce 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
}

Quando si usa C# 8 o versione successiva, la await foreach sintassi può essere usata per leggere i messaggi. Il IAsyncStreamReader<T>.ReadAllAsync() metodo di estensione legge tutti i messaggi dal flusso di risposta:

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
}

Chiamata di streaming client

Viene avviata una chiamata di streaming client senza che il client invii un messaggio. Il client può scegliere di inviare messaggi con RequestStream.WriteAsync. Al termine dell'invio dei messaggi, RequestStream.CompleteAsync() il client deve essere chiamato per notificare al servizio. La chiamata viene completata quando il servizio restituisce un messaggio di risposta.

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

Chiamata di streaming bidirezionale

Una chiamata di streaming bidirezionale viene avviata senza che il client invii un messaggio. Il client può scegliere di inviare messaggi con RequestStream.WriteAsync. I messaggi trasmessi dal servizio sono accessibili con ResponseStream.MoveNext() o ResponseStream.ReadAllAsync(). La chiamata di streaming bidirezionale viene completata quando non ResponseStream contiene più messaggi.

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;

Per ottenere prestazioni ottimali e per evitare errori non necessari nel client e nel servizio, provare a completare correttamente le chiamate di streaming bidirezionali. Una chiamata bidirezionale viene completata normalmente al termine della lettura del flusso di richiesta da parte del server e il client ha terminato di leggere il flusso di risposta. La chiamata di esempio precedente è un esempio di una chiamata bidirezionale che termina normalmente. Nella chiamata, il client:

  1. Avvia una nuova chiamata di streaming bidirezionale chiamando EchoClient.Echo.
  2. Crea un'attività in background per leggere i messaggi dal servizio usando ResponseStream.ReadAllAsync().
  3. Invia messaggi al server con RequestStream.WriteAsync.
  4. Notifica al server che ha terminato l'invio di messaggi con RequestStream.CompleteAsync().
  5. Attende che l'attività in background non abbia letto tutti i messaggi in arrivo.

Durante una chiamata di streaming bidirezionale, il client e il servizio possono inviare messaggi l'uno all'altro in qualsiasi momento. La logica client migliore per interagire con una chiamata bidirezionale varia a seconda della logica del servizio.

Accedere alle intestazioni gRPC

gRPC chiama le intestazioni di risposta restituite. Le intestazioni di risposta HTTP passano metadati nome/valore relativi a una chiamata che non è correlata al messaggio restituito.

Le intestazioni sono accessibili tramite ResponseHeadersAsync, che restituisce una raccolta di metadati. Le intestazioni vengono in genere restituite con il messaggio di risposta; quindi, è necessario attenderli.

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;

ResponseHeadersAsync Utilizzo:

  • Deve attendere il risultato di ResponseHeadersAsync per ottenere la raccolta di intestazioni.
  • Non è necessario accedere prima ResponseAsync (o al flusso di risposta durante lo streaming). Se viene restituita una risposta, restituisce ResponseHeadersAsync immediatamente le intestazioni.
  • Genererà un'eccezione se si è verificato un errore di connessione o server e le intestazioni non sono state restituite per la chiamata gRPC.

Accedere ai trailer gRPC

Le chiamate gRPC possono restituire trailer di risposta. I trailer vengono usati per fornire metadati nome/valore relativi a una chiamata. I trailer offrono funzionalità simili alle intestazioni HTTP, ma vengono ricevute alla fine della chiamata.

I trailer sono accessibili tramite GetTrailers(), che restituisce una raccolta di metadati. I trailer vengono restituiti al termine della risposta. Pertanto, è necessario attendere tutti i messaggi di risposta prima di accedere ai trailer.

Le chiamate di streaming unarie e client devono essere in attesa ResponseAsync prima di chiamare 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");

Le chiamate di streaming bidirezionali e del server devono terminare in attesa del flusso di risposta prima di chiamare 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");

I trailer sono accessibili anche da RpcException. Un servizio può restituire trailer insieme a uno stato gRPC non OK. In questo caso, i trailer vengono recuperati dall'eccezione generata dal client 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");
}

Configurare la scadenza

La configurazione di una scadenza di chiamata gRPC è consigliata perché fornisce un limite superiore per quanto tempo può essere eseguita una chiamata. Impedisce che i servizi non funzionano per sempre e esauriscono le risorse del server. Le scadenze sono uno strumento utile per la creazione di app affidabili.

Configurare CallOptions.Deadline per impostare una scadenza per una chiamata 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.");
}

Per altre informazioni, vedere Reliable gRPC services with deadline and cancellation .For more information, see Reliable gRPC services with deadline and cancellation.

Risorse aggiuntive