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 ai metodi di streaming unary, server streaming, streaming client e streaming bidirezionale.

Configurare il client gRPC

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

Viene creato un client gRPC 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 opzioni correlate alla chiamata di un servizio. Ad esempio, l'oggetto HttpClient usato per effettuare chiamate, la dimensione massima di invio e ricezione dei messaggi e la registrazione possono essere specificate GrpcChannelOptions in 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 denominato. 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 da usare 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, assicurarsi 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

Canale e prestazioni client e utilizzo:

  • La creazione di un canale può essere un'operazione costosa. La riutilizzo di un canale per le chiamate gRPC offre vantaggi per le prestazioni.
  • I client gRPC vengono creati con 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 servizi gRPC da un'app ASP.NET Core, prendere in considerazione l'integrazione della factory client gRPC. Integrazione 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. Stiamo lavorando per migliorare il supporto HTTP/2 in una versione futura di Xamarin. Grpc.Core e gRPC-Web sono alternative utilizzabili oggi.

Effettuare chiamate gRPC

Una chiamata gRPC viene avviata chiamando un metodo nel 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 viene usato il client per eseguire 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 unario inizia con il client che invia un messaggio di richiesta. Viene restituito un messaggio di risposta al termine del servizio.

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 comporterà due metodi .NET sul tipo di client gRPC concreto per chiamare il metodo: un metodo asincrono e un metodo di blocco. Ad esempio, 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 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 viene completata 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 di 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

Viene avviata una chiamata di streaming bidirezionale 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 l'oggetto ResponseStream non ha 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 prestazioni ottimali e per evitare errori non necessari nel client e nel servizio, provare a completare le chiamate di streaming bidirezionali in modo corretto. Una chiamata bidirezionale viene completata correttamente al termine della lettura del flusso di richiesta e il client ha completato la lettura del 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 completato l'invio di messaggi con RequestStream.CompleteAsync().
  5. Attende fino a quando l'attività in background ha letto tutti i messaggi in ingresso.

Durante una chiamata di streaming bidirezionale, il client e il servizio possono inviare messaggi tra loro 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 intestazioni di risposta restituite. Le intestazioni di risposta HTTP passano i metadati nome/valore relativi a una chiamata che non è correlata al messaggio restituito.

Le intestazioni sono accessibili usando ResponseHeadersAsync, che restituisce una raccolta di metadati. Le intestazioni vengono in genere restituite con il messaggio di risposta; pertanto, è 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 l'insieme intestazioni.
  • Non è necessario accedere a 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 forniscono funzionalità simili alle intestazioni HTTP, ma vengono ricevuti alla fine della chiamata.

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

Le chiamate di streaming unary e client devono essere attese 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 server devono terminare l'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 questa situazione, 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

È consigliabile configurare una scadenza di chiamata gRPC perché fornisce un limite superiore per quanto tempo può essere eseguita una chiamata. Impedisce l'esecuzione dei servizi non funzionanti per sempre e l'esaurimento delle 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 Servizi gRPC affidabili con scadenze e annullamento.

Risorse aggiuntive