Come usare il client gRPC in Delphi

· Componenti

Il componente TsgcGRPCClient porta gRPC su Delphi e C++Builder senza alcun runtime esterno. Fa parte dell'edizione Enterprise di sgcWebSockets e gira su Windows, macOS, Linux, iOS e Android. Questa guida spiega come funziona il client, come configurarlo e come effettuare ciascuno dei quattro tipi di chiamata gRPC da Delphi.

Come funziona

gRPC sono messaggi Protocol Buffers incapsulati in frame su HTTP/2. Ogni messaggio viene inviato come payload con prefisso di lunghezza su uno stream HTTP/2, la richiesta porta header come content-type: application/grpc e grpc-timeout, e lo stato finale arriva nei trailer grpc-status e grpc-message.

sgcWebSockets include già uno stack HTTP/2 completo, quindi TsgcGRPCClient si appoggia su un transport TsgcHTTP2Client. Tu gli fornisci i byte della richiesta serializzata, lui si occupa del framing, degli header e del timeout, apre lo stream HTTP/2 e analizza la risposta e i trailer riconvertendoli in una TsgcGRPCResponse tipizzata. Dato che il client lavora con byte grezzi TBytes, puoi abbinarlo a qualsiasi libreria Protocol Buffers tu già usi.

Configurare il client

Un canale gRPC è una connessione HTTP/2. Crea un TsgcHTTP2Client, puntalo sull'host e sulla porta, poi assegnalo alla proprietà Client del componente gRPC. Per il TLS, il client HTTP/2 usa OpenSSL o Windows SChannel; per un server locale in chiaro (h2c), imposta TLS a False.

uses
  sgcHTTP2, sgcGRPC_Client, sgcGRPC_Classes, sgcGRPC_Types;

var
  HTTP2: TsgcHTTP2Client;
  GRPC: TsgcGRPCClient;
begin
  HTTP2 := TsgcHTTP2Client.Create(nil);
  HTTP2.Host := 'grpc.example.com';
  HTTP2.Port := 443;
  HTTP2.TLS  := True;                 // False for a local h2c server
  HTTP2.TLSOptions.IOHandler := iohOpenSSL;  // or iohSChannel on Windows

  GRPC := TsgcGRPCClient.Create(nil);
  GRPC.Client := HTTP2;
end;

Configurare il canale

Le impostazioni valide per l'intero canale risiedono in ChannelOptions. Puoi attivare la compressione gzip per ogni messaggio, scegliere il content type sul wire e aumentare i limiti di dimensione dei messaggi e dei metadata.

// gzip-compress outgoing messages
GRPC.ChannelOptions.Compression := grpcGzip;          // grpcNoCompression, grpcGzip, grpcDeflate, grpcSnappy

// application/grpc+proto (default) or application/grpc+json
GRPC.ChannelOptions.ContentType := grpcProto;         // or grpcJSON

// raise the limits for large messages
GRPC.ChannelOptions.MaxMessageSize  := 16 * 1024 * 1024;
GRPC.ChannelOptions.MaxMetadataSize := 64 * 1024;

Metadata e autenticazione

Gli header personalizzati viaggiano come metadata gRPC. Aggiungili a DefaultMetadata per inviarli a ogni chiamata, che è il posto consueto per un token di autorizzazione o un header di tracing.

GRPC.DefaultMetadata.Add('authorization', 'Bearer eyJ...');
GRPC.DefaultMetadata.Add('x-api-key', 'my-api-key');

Deadline e annullamento

Un timeout per chiamata viene inviato come header standard grpc-timeout, così il server può rinunciare nello stesso momento in cui lo fa il tuo client. Una chiamata in streaming in corso può essere interrotta in qualsiasi momento con CancelCall, passando lo stream id restituito al momento dell'apertura dello stream.

Chiamate unary

Una chiamata unary invia una richiesta e ottiene una risposta. Call blocca finché la risposta non arriva e restituisce una TsgcGRPCResponse con StatusCode, StatusMessage, i byte grezzi Data (o DataString) e i Trailers.

var
  oResponse: TsgcGRPCResponse;
begin
  oResponse := GRPC.Call('helloworld.Greeter', 'SayHello', RequestBytes);
  if oResponse.StatusCode = grpcOK then
    Memo1.Text := oResponse.DataString
  else
    ShowMessage('gRPC error ' + oResponse.StatusMessage);
end;

Per mantenere reattiva l'interfaccia, usa CallAsync. Ritorna immediatamente e consegna la risposta tramite l'evento OnGRPCResponse.

GRPC.OnGRPCResponse := GRPCResponse;
GRPC.CallAsync('helloworld.Greeter', 'SayHello', RequestBytes);

procedure TForm1.GRPCResponse(Sender: TObject; const Response: TsgcGRPCResponse);
begin
  if Response.StatusCode = grpcOK then
    Memo1.Text := Response.DataString;
end;

Server streaming

Con il server streaming invii una richiesta e il server restituisce uno stream di messaggi. Ogni messaggio genera OnGRPCStreamMessage, e OnGRPCStreamEnd scatta una volta con lo stato finale.

GRPC.OnGRPCStreamMessage := GRPCStreamMessage;
GRPC.OnGRPCStreamEnd := GRPCStreamEnd;

GRPC.ServerStreamingCall('chat.Feed', 'Subscribe', RequestBytes);

procedure TForm1.GRPCStreamMessage(Sender: TObject; aStreamId: Integer;
  const aData: TBytes);
begin
  Memo1.Lines.Add(DecodeMessage(aData));
end;

Client streaming

Il client streaming è l'immagine speculare: invii uno stream di messaggi e il server risponde una sola volta. Apri lo stream, invia ogni messaggio, poi chiudilo per leggere l'unica risposta.

var
  vStreamId: Integer;
  oResponse: TsgcGRPCResponse;
begin
  vStreamId := GRPC.OpenClientStream('upload.Service', 'Send');
  GRPC.SendStreamMessage(vStreamId, ChunkBytes1);
  GRPC.SendStreamMessage(vStreamId, ChunkBytes2);
  oResponse := GRPC.CloseClientStream(vStreamId);
  if oResponse.StatusCode = grpcOK then
    ShowMessage('Upload accepted');
end;

Streaming bidirezionale

Lo streaming bidirezionale esegue uno scambio full-duplex su un unico stream HTTP/2: entrambe le parti inviano quando vogliono. Apri lo stream, invia i messaggi quando serve, gestisci i messaggi in arrivo in OnGRPCStreamMessage e chiudi al termine.

var
  vStreamId: Integer;
begin
  vStreamId := GRPC.OpenBidiStream('chat.Room', 'Connect');
  GRPC.SendBidiMessage(vStreamId, EncodeMessage('hello'));
  // ... incoming messages arrive on OnGRPCStreamMessage ...
  GRPC.CloseBidiStream(vStreamId);
end;

Status code e gestione degli errori

Ogni risposta porta uno StatusCode tipizzato, da grpcOK attraverso il set standard di gRPC (grpcNOT_FOUND, grpcUNAVAILABLE, grpcDEADLINE_EXCEEDED, grpcUNAUTHENTICATED e gli altri). Per le chiamate asincrone e in streaming, OnGRPCError segnala uno stato gRPC diverso da OK e OnGRPCException segnala un guasto di transport o di connessione.

GRPC.OnGRPCError := GRPCError;

procedure TForm1.GRPCError(Sender: TObject; aStreamId: Integer;
  aStatusCode: TsgcGRPCStatusCode; const aStatusMessage: string);
begin
  Memo1.Lines.Add(Format('gRPC error %d: %s', [Ord(aStatusCode), aStatusMessage]));
end;

Retry automatico

Il client può ritentare per te le chiamate fallite. Abilita RetryPolicy, imposta il numero di tentativi e il backoff, ed elenca gli status code che devono attivare un retry. Questa è la configurazione tipica per i guasti transitori grpcUNAVAILABLE.

GRPC.RetryPolicy.Enabled := True;
GRPC.RetryPolicy.MaxAttempts := 4;
GRPC.RetryPolicy.InitialBackoff := 200;     // ms
GRPC.RetryPolicy.BackoffMultiplier := 2.0;
GRPC.RetryPolicy.RetryableStatusCodes :=
  GRPC.RetryPolicy.RetryableStatusCodes + [grpcUNAVAILABLE, grpcRESOURCE_EXHAUSTED];

Per logica trasversale come il logging o il refresh di un token, aggiungi una voce alla catena Interceptors, che avvolge ogni chiamata in un unico punto.

Dove andare poi

Sopra questo client generico, sgcWebSockets include client tipizzati per i servizi gRPC di Google Cloud (Pub/Sub, Speech-to-Text, Translation, Vision, Natural Language, Cloud Storage, BigQuery e Vertex AI), così richiami metodi di alto livello invece di assemblare protobuf a mano. Un esempio pronto all'uso si trova in Demos\21.GRPC\01.GRPC_Client, e il riferimento completo è sulla pagina prodotto del client gRPC.

Domande o aiuto per iniziare? Contattaci. Riceverai una risposta dalle persone che hanno scritto il codice.