Jak korzystać z klienta gRPC w Delphi

· Komponenty

Komponent TsgcGRPCClient dostarcza gRPC do Delphi i C++Builder bez żadnego zewnętrznego środowiska uruchomieniowego. Jest częścią edycji sgcWebSockets Enterprise i działa w systemach Windows, macOS, Linux, iOS oraz Android. Ten przewodnik wyjaśnia, jak klient działa, jak go skonfigurować i jak wykonać każdy z czterech typów wywołań gRPC w Delphi.

Jak to działa

gRPC to komunikaty Protocol Buffers oprawione w ramki na HTTP/2. Każdy komunikat jest wysyłany jako ładunek z prefiksem długości w strumieniu HTTP/2, żądanie niesie nagłówki takie jak content-type: application/grpc oraz grpc-timeout, a końcowy status pojawia się jako zwiastuny (trailers) grpc-status i grpc-message.

sgcWebSockets dostarcza już kompletny stos HTTP/2, więc TsgcGRPCClient opiera się na transporcie TsgcHTTP2Client. Przekazujesz mu zserializowane bajty żądania, a on wykonuje opakowanie w ramki, obsługę nagłówków i terminów, otwiera strumień HTTP/2 oraz parsuje odpowiedź i zwiastuny z powrotem do typowanego TsgcGRPCResponse. Ponieważ klient operuje na surowych TBytes, możesz połączyć go z dowolną biblioteką Protocol Buffers, której już używasz.

Konfiguracja klienta

Kanał gRPC to połączenie HTTP/2. Utwórz TsgcHTTP2Client, wskaż mu host i port, a następnie przypisz go do właściwości Client komponentu gRPC. Dla TLS klient HTTP/2 używa OpenSSL lub Windows SChannel; dla lokalnego serwera bez szyfrowania (h2c) ustaw TLS na 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;

Konfiguracja kanału

Ustawienia obejmujące cały kanał znajdują się w ChannelOptions. Możesz włączyć kompresję gzip dla poszczególnych komunikatów, wybrać typ zawartości przesyłanej na łączu oraz podnieść limity rozmiaru komunikatu i metadanych.

// 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;

Metadane i uwierzytelnianie

Niestandardowe nagłówki przesyłane są jako metadane gRPC. Dodaj je do DefaultMetadata, aby wysyłać je przy każdym wywołaniu, co jest typowym miejscem na token autoryzacyjny lub nagłówek śledzenia.

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

Terminy i anulowanie

Limit czasu dla pojedynczego wywołania jest wysyłany jako standardowy nagłówek grpc-timeout, dzięki czemu serwer może zrezygnować w tym samym momencie co klient. Trwające wywołanie strumieniowe można w dowolnej chwili zatrzymać za pomocą CancelCall, przekazując identyfikator strumienia zwrócony podczas jego otwierania.

Wywołania unarne

Wywołanie unarne wysyła jedno żądanie i otrzymuje jedną odpowiedź. Call blokuje do czasu nadejścia odpowiedzi i zwraca TsgcGRPCResponse z polami StatusCode, StatusMessage, surowymi bajtami Data (lub DataString) oraz 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;

Aby zachować responsywność interfejsu, użyj CallAsync. Zwraca on natychmiast i dostarcza odpowiedź poprzez zdarzenie 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;

Strumieniowanie po stronie serwera

Przy strumieniowaniu po stronie serwera wysyłasz jedno żądanie, a serwer zwraca strumień komunikatów. Każdy komunikat wywołuje OnGRPCStreamMessage, a OnGRPCStreamEnd uruchamia się raz, z końcowym statusem.

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;

Strumieniowanie po stronie klienta

Strumieniowanie po stronie klienta jest lustrzanym odbiciem: wysyłasz strumień komunikatów, a serwer odpowiada raz. Otwórz strumień, wyślij każdy komunikat, a następnie zamknij go, aby odczytać pojedynczą odpowiedź.

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;

Strumieniowanie dwukierunkowe

Strumieniowanie dwukierunkowe prowadzi wymianę w trybie pełnego dupleksu przez pojedynczy strumień HTTP/2: obie strony wysyłają, kiedy tylko chcą. Otwórz strumień, wysyłaj komunikaty według potrzeb, obsługuj przychodzące komunikaty w OnGRPCStreamMessage i zamknij go po zakończeniu.

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

Kody statusu i obsługa błędów

Każda odpowiedź niesie typowany StatusCode od grpcOK przez standardowy zestaw gRPC (grpcNOT_FOUND, grpcUNAVAILABLE, grpcDEADLINE_EXCEEDED, grpcUNAUTHENTICATED i pozostałe). Dla wywołań asynchronicznych i strumieniowych OnGRPCError zgłasza status gRPC inny niż OK, a OnGRPCException zgłasza awarię transportu lub połączenia.

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;

Automatyczne ponawianie prób

Klient może ponawiać za Ciebie nieudane wywołania. Włącz RetryPolicy, ustaw liczbę prób i opóźnienie wycofania, a także wypisz kody statusu, które powinny wyzwalać ponowienie. To typowa konfiguracja dla przejściowych awarii 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];

Dla logiki przekrojowej, takiej jak logowanie czy odświeżanie tokena, dodaj wpis do łańcucha Interceptors, który oprawia każde wywołanie w jednym miejscu.

Co dalej

Na bazie tego generycznego klienta sgcWebSockets dostarcza typowane klienty dla usług gRPC Google Cloud (Pub/Sub, Speech-to-Text, Translation, Vision, Natural Language, Cloud Storage, BigQuery i Vertex AI), dzięki czemu wywołujesz metody wysokiego poziomu zamiast ręcznie składać protobufy. Gotowy do uruchomienia przykład znajduje się w Demos\21.GRPC\01.GRPC_Client, a pełną dokumentację znajdziesz na stronie produktu klienta gRPC.

Masz pytania lub potrzebujesz pomocy na początek? Skontaktuj się z nami. Otrzymasz odpowiedź od ludzi, którzy napisali ten kod.