在 Delphi 中通过 gRPC 使用 Google Cloud Storage

· 组件

Google Cloud Storage 是 Google 的对象存储服务。数据存放在存储桶中,每个存储桶保存若干对象(文件),你可以上传、列出、读取和删除它们。大多数 Delphi 代码通过 JSON REST API 访问它,但该存储服务也提供一个 gRPC API,它通过 HTTP/2 使用 Protocol Buffers。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,并将它作为一个 Bearer 令牌放在 gRPC 的 authorization 元数据中,这是 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_Endpoint 必须指向 storage.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;

列出存储桶

要枚举一个项目中的存储桶,请填充一个 TsgcGRPCStorageListBucketsRequest,将项目作为 Parent,序列化它,并向 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 序列化,因此无论你是在一次调用中上传一个小文件,还是分片流式传输一个大对象,同一套强类型类都能工作。

分页和错误处理

当有更多结果可用时,列表响应会返回一个 NextPageToken。将它复制到下一个请求的 PageToken 中并再次调用,即可翻阅大型存储桶或长对象列表。在错误一侧,每个 TsgcGRPCResponse 都携带一个强类型的 StatusCode,因此一个非 grpcOK 的结果会准确告诉你出了什么问题,从表示令牌错误或缺失的 grpcUNAUTHENTICATED,到表示存储桶缺失的 grpcNOT_FOUND。对于异步工作,OnGRPCResponseOnGRPCErrorOnGRPCException 通过事件传递相同的信息。

可用性

强类型 Google Cloud Storage gRPC 客户端是 sgcWebSockets Enterprise 版本的一部分。一个完整、开箱即用的示例位于 Demos\21.GRPC\15.Cloud_Storage,涵盖身份验证、列出和创建存储桶以及列出对象。完整参考资料请见gRPC Client 产品页面

有问题或反馈?联系我们。你会收到来自编写这段代码的人的回复。