Delphi で gRPC クライアントを使う方法

· コンポーネント

TsgcGRPCClient コンポーネントは、外部ランタイムを一切必要とせずに gRPC を Delphi と C++Builder にもたらします。sgcWebSockets Enterprise エディションの一部であり、Windows、macOS、Linux、iOS、Android 上で動作します。本ガイドでは、このクライアントの仕組み、設定方法、そして Delphi から 4 種類の gRPC 呼び出しをそれぞれ行う方法を説明します。

仕組み

gRPC は HTTP/2 上でフレーム化された Protocol Buffers メッセージです。各メッセージは HTTP/2 ストリーム上で長さプレフィックス付きのペイロードとして送信され、リクエストには content-type: application/grpcgrpc-timeout といったヘッダーが付与され、最終的なステータスは grpc-status および grpc-message トレーラーとして届きます。

sgcWebSockets にはすでに完全な HTTP/2 スタックが搭載されているため、TsgcGRPCClientTsgcHTTP2Client トランスポートの上に乗ります。シリアライズ済みのリクエスト バイトを渡すと、フレーム化、ヘッダー、タイムアウトの処理を行い、HTTP/2 ストリームを開き、レスポンスとトレーラーを型付きの TsgcGRPCResponse に解析して返します。このクライアントは生の TBytes を扱うため、すでに使用している任意の Protocol Buffers ライブラリと組み合わせることができます。

クライアントのセットアップ

gRPC チャネルは HTTP/2 接続です。TsgcHTTP2Client を作成し、ホストとポートを指定してから、gRPC コンポーネントの Client プロパティに割り当てます。TLS には HTTP/2 クライアントが OpenSSL または Windows SChannel を使用します。ローカルの平文サーバー(h2c)の場合は TLSFalse に設定します。

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;

チャネルの設定

チャネル全体の設定は ChannelOptions にあります。メッセージごとの gzip 圧縮の有効化、ワイヤー コンテンツ タイプの選択、メッセージおよび 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 と認証

カスタム ヘッダーは gRPC metadata として送られます。すべての呼び出しで送信するには DefaultMetadata に追加します。これは認可トークンやトレーシング ヘッダーを置く一般的な場所です。

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

デッドラインとキャンセル

呼び出しごとのタイムアウトは標準の grpc-timeout ヘッダーとして送信されるため、サーバーはクライアントと同じタイミングで処理を中断できます。実行中のストリーミング呼び出しは、ストリームを開いたときに返されたストリーム id を渡すことで、CancelCall によっていつでも停止できます。

unary 呼び出し

unary 呼び出しは 1 つのリクエストを送り、1 つのレスポンスを受け取ります。Call は応答が届くまでブロックし、StatusCodeStatusMessage、生の Data バイト(または DataString)、そして Trailers を持つ TsgcGRPCResponse を返します。

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;

UI の応答性を保つには CallAsync を使用します。これはすぐに復帰し、応答を 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;

サーバー ストリーミング

サーバー ストリーミングでは、1 つのリクエストを送ると、サーバーがメッセージのストリームを返します。各メッセージは OnGRPCStreamMessage を発生させ、OnGRPCStreamEnd は最終ステータスとともに 1 回だけ発火します。

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;

クライアント ストリーミング

クライアント ストリーミングはちょうど逆で、メッセージのストリームを送り出すと、サーバーが 1 回だけ応答します。ストリームを開き、各メッセージを送信してから、ストリームを閉じて単一の応答を読み取ります。

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;

双方向ストリーミング

双方向ストリーミングは、単一の HTTP/2 ストリーム上で全二重のやり取りを行います。双方が好きなタイミングで送信できます。ストリームを開き、必要に応じてメッセージを送信し、受信メッセージを OnGRPCStreamMessage で処理して、終わったら閉じます。

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

ステータス コードとエラー処理

すべてのレスポンスは、grpcOK から標準の gRPC セット全体(grpcNOT_FOUNDgrpcUNAVAILABLEgrpcDEADLINE_EXCEEDEDgrpcUNAUTHENTICATED など)にわたる型付きの StatusCode を持ちます。非同期呼び出しおよびストリーミング呼び出しでは、OnGRPCError が OK 以外の gRPC ステータスを通知し、OnGRPCException がトランスポートまたは接続の失敗を通知します。

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;

自動リトライ

このクライアントは失敗した呼び出しを自動的にリトライできます。RetryPolicy を有効にし、試行回数とバックオフを設定し、リトライをトリガーすべきステータス コードを列挙します。これは一時的な 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];

ロギングやトークンの更新といった横断的なロジックには、Interceptors チェーンにエントリを追加します。これはすべての呼び出しを一か所でラップします。

次のステップ

この汎用クライアントの上に、sgcWebSockets は Google Cloud gRPC サービス(Pub/Sub、Speech-to-Text、Translation、Vision、Natural Language、Cloud Storage、BigQuery、Vertex AI)向けの型付きクライアントを搭載しているため、protobuf を手作業で組み立てる代わりに高レベルなメソッドを呼び出せます。すぐに実行できるサンプルは Demos\21.GRPC\01.GRPC_Client にあり、完全なリファレンスは gRPC Client 製品ページにあります。

ご質問や導入のお手伝いが必要ですか? お問い合わせください。コードを書いた本人たちから返信が届きます。