Delphi で gRPC を使う Google Cloud Storage

· コンポーネント

Google Cloud Storage は Google のオブジェクトストレージサービスです。データはバケットに存在し、各バケットには、アップロード、一覧取得、読み取り、削除を行うオブジェクト(ファイル)が含まれます。ほとんどの Delphi コードは JSON REST API 経由でこれにアクセスしますが、ストレージサービスは HTTP/2 上で Protocol Buffers を話す gRPC API も公開しています。sgcWebSockets Enterprise には TsgcGRPCClient の上に構築された型付き Cloud Storage gRPC クライアントが付属しているため、外部ランタイムや生成されたスタブなしで、Delphi や C++Builder からバケットとオブジェクトを操作できます。

仕組み

gRPC Storage API は storage.googleapis.com:443 に存在し、google.storage.v2.Storage サービスを話します。sgcWebSockets には完全な HTTP/2 スタックがすでに付属しているため、型付きクライアントは TLS を有効にした TsgcHTTP2Client トランスポートの上に配置されます。TsgcGRPCClient が gRPC のフレーミング、ヘッダー、タイムアウト処理を行い、HTTP/2 ストリームを開き、レスポンスとトレーラーを TsgcGRPCResponse に解析して返します。

型付きレイヤーは sgcGRPC_Google_Storage ユニットにあります。TsgcGRPCStorageListBucketsRequestTsgcGRPCStorageListObjectsResponseTsgcGRPCStorageBucket のように、Protocol Buffers との間で自身をシリアライズするリクエスト/レスポンスクラスを提供します。型付きプロパティを設定し、ToBytes を呼び出してリクエストペイロードを取得し、Call で送信し、LoadFromBytes でレスポンスを読み込みます。protobuf コンパイラも、手書きのフィールドタグも不要です。

認証には Google サービスアカウントを使用します。クライアントはサービスアカウントキーから JSON Web Token に署名し、それを gRPC の authorization メタデータに Bearer トークンとして提示します。これは Google Cloud がサーバー間呼び出しを認可する標準的な方法です。

トランスポートのセットアップ

gRPC チャネルは単なる HTTP/2 接続です。TsgcHTTP2Client を TLS 有効でストレージエンドポイントに向け、それを TsgcGRPCClient にアタッチして、バイナリ Protocol Buffers コンテンツタイプを選択します。

uses
  sgcHTTP2_Client, sgcGRPC_Client, sgcGRPC_Types, sgcGRPC_Google_Storage;

var
  HTTP2: TsgcHTTP2Client;
  GRPC: TsgcGRPCClient;
begin
  HTTP2 := TsgcHTTP2Client.Create(nil);
  HTTP2.Host := 'storage.googleapis.com';
  HTTP2.Port := 443;
  HTTP2.TLS  := True;

  GRPC := TsgcGRPCClient.Create(nil);
  GRPC.Client := HTTP2;
  GRPC.ChannelOptions.ContentType := grpcProto;
  GRPC.ChannelOptions.Compression := grpcNoCompression;

  HTTP2.Active := True;
end;

サービスアカウントによる認証

サービスアカウントの JSON キーを読み込み、ストレージエンドポイント用に署名した JWT を構成し、得られたトークンを DefaultMetadata に配置すると、すべての呼び出しに付随します。自己署名のサービスアカウント JWT はオーディエンスにバインドされるため、トークンが受け入れられるには API_Endpointstorage.googleapis.com を指している必要があります。

var
  Cloud: TsgcHTTPGoogleCloud_PubSub_Client;
begin
  Cloud := TsgcHTTPGoogleCloud_PubSub_Client.Create(nil);
  Cloud.LoadSettingsFromFile('service-account.json');
  Cloud.GoogleCloudOptions.Authentication := gcaJWT;
  Cloud.GoogleCloudOptions.JWT.API_Endpoint := 'https://storage.googleapis.com/';

  // OnAuthToken fires when the token is ready; copy it into the gRPC metadata
  GRPC.DefaultMetadata.Clear;
  GRPC.DefaultMetadata.Add('authorization', 'Bearer ' + Token);
end;

バケットの一覧取得

プロジェクト内のバケットを列挙するには、プロジェクトを Parent として TsgcGRPCStorageListBucketsRequest に値を入れ、シリアライズし、ListBuckets メソッドへユーナリの Call を行います。レスポンスのバイト列はそのまま TsgcGRPCStorageListBucketsResponse に読み込まれ、各バケットを名前とロケーションとともに公開します。

var
  oRequest: TsgcGRPCStorageListBucketsRequest;
  oResponse: TsgcGRPCResponse;
  oBuckets: TsgcGRPCStorageListBucketsResponse;
  i: Integer;
begin
  oRequest := TsgcGRPCStorageListBucketsRequest.Create;
  try
    oRequest.Parent := 'projects/' + ProjectId;
    oRequest.PageSize := 100;

    oResponse := GRPC.Call('google.storage.v2.Storage', 'ListBuckets',
      oRequest.ToBytes);
  finally
    oRequest.Free;
  end;

  if oResponse.StatusCode = grpcOK then
  begin
    oBuckets := TsgcGRPCStorageListBucketsResponse.Create;
    try
      oBuckets.LoadFromBytes(oResponse.Data);
      for i := 0 to oBuckets.BucketCount - 1 do
        Memo1.Lines.Add(oBuckets.Bucket(i).Name + ' (location: ' +
          oBuckets.Bucket(i).Location + ')');
    finally
      oBuckets.Free;
    end;
  end;
end;

バケットの作成

バケットの作成も、TsgcGRPCStorageCreateBucketRequest を使って同じパターンに従います。親プロジェクト、新しいバケット ID、そしてバケットリソースを所有するプロジェクトを設定してから、CreateBucket を呼び出します。レスポンスには、作成された TsgcGRPCStorageBucket が含まれます。

var
  oRequest: TsgcGRPCStorageCreateBucketRequest;
  oResponse: TsgcGRPCResponse;
begin
  oRequest := TsgcGRPCStorageCreateBucketRequest.Create;
  try
    oRequest.Parent := 'projects/' + ProjectId;
    oRequest.BucketId := 'my-new-bucket';
    oRequest.Bucket.Project := 'projects/' + ProjectId;

    oResponse := GRPC.Call('google.storage.v2.Storage', 'CreateBucket',
      oRequest.ToBytes);
  finally
    oRequest.Free;
  end;
end;

バケット内のオブジェクトの一覧取得

バケット内のオブジェクトの一覧取得には TsgcGRPCStorageListObjectsRequest を使います。親はバケットリソース名で、projects/_/buckets/<bucket> の形式です。レスポンスは各オブジェクトの名前、サイズ、コンテンツタイプを提供し、必要に応じて PrefixDelimiter を設定してフォルダーのような階層をたどることもできます。

var
  oRequest: TsgcGRPCStorageListObjectsRequest;
  oResponse: TsgcGRPCResponse;
  oObjects: TsgcGRPCStorageListObjectsResponse;
  i: Integer;
begin
  oRequest := TsgcGRPCStorageListObjectsRequest.Create;
  try
    oRequest.Parent := 'projects/_/buckets/' + BucketName;
    oRequest.PageSize := 100;

    oResponse := GRPC.Call('google.storage.v2.Storage', 'ListObjects',
      oRequest.ToBytes);
  finally
    oRequest.Free;
  end;

  if oResponse.StatusCode = grpcOK then
  begin
    oObjects := TsgcGRPCStorageListObjectsResponse.Create;
    try
      oObjects.LoadFromBytes(oResponse.Data);
      for i := 0 to oObjects.ObjectCount - 1 do
        Memo1.Lines.Add(oObjects.Object_(i).Name + ' (size: ' +
          IntToStr(oObjects.Object_(i).Size) + ', type: ' +
          oObjects.Object_(i).ContentType + ')');
    finally
      oObjects.Free;
    end;
  end;
end;

オブジェクトデータの読み書き

バケットとオブジェクトのメタデータに加えて、このユニットはオブジェクトのペイロードもカバーします。TsgcGRPCStorageReadObjectRequest はオブジェクトからバイト列を読み戻し、範囲ダウンロード用に省略可能な ReadOffsetReadLimit を持ち、対応するレスポンスが生の Data バイト列を公開します。TsgcGRPCStorageWriteObjectRequest はオブジェクトのコンテンツをチャンクでアップロードします。最初のチャンクに WriteObjectSpec リソースを設定し、WriteOffset の値を増やしながら Data をプッシュし、最後のチャンクで FinishWrite をマークします。すべてのメッセージが ToBytesLoadFromBytes を通じてシリアライズされるため、小さなファイルを 1 回の呼び出しでアップロードする場合も、大きなオブジェクトを分割してストリーミングする場合も、同じ型付きクラスが機能します。

ページネーションとエラー処理

一覧レスポンスは、さらに結果がある場合に NextPageToken を返します。それを次のリクエストの PageToken にコピーして再度呼び出すと、大きなバケットや長いオブジェクト一覧をページ送りできます。エラー側では、すべての TsgcGRPCResponse が型付きの StatusCode を持つため、grpcOK 以外の結果は何が問題だったかを正確に教えてくれます。トークンが不正または欠落している場合の grpcUNAUTHENTICATED から、バケットが見つからない場合の grpcNOT_FOUND までです。非同期処理では、OnGRPCResponseOnGRPCErrorOnGRPCException が同じ情報をイベント経由で配信します。

提供状況

型付き Google Cloud Storage gRPC クライアントは sgcWebSockets Enterprise エディションの一部です。認証、バケットの一覧取得と作成、オブジェクトの一覧取得をカバーする、すぐに実行できる完全なサンプルは Demos\21.GRPC\15.Cloud_Storage にあります。完全なリファレンスは gRPC Client 製品ページにあります。

ご質問やご意見はありますか? お問い合わせください。コードを書いた本人から返信いたします。