Google Cloud Storage sobre gRPC en Delphi

· Componentes

Google Cloud Storage es el servicio de almacenamiento de objetos de Google. Los datos viven en buckets, y cada bucket contiene objetos (archivos) que subes, listas, lees y eliminas. La mayoría del código Delphi lo alcanza a través de la API REST JSON, pero el servicio de almacenamiento también expone una API gRPC que habla Protocol Buffers sobre HTTP/2. sgcWebSockets Enterprise incluye un cliente gRPC de Cloud Storage tipado construido sobre TsgcGRPCClient, así que puedes manejar buckets y objetos desde Delphi y C++Builder sin ningún runtime externo ni stubs generados.

Cómo funciona

La API gRPC de Storage vive en storage.googleapis.com:443 y habla el servicio google.storage.v2.Storage. sgcWebSockets ya incluye una pila HTTP/2 completa, así que el cliente tipado se apoya sobre un transporte TsgcHTTP2Client con TLS habilitado. TsgcGRPCClient hace el enmarcado gRPC, las cabeceras y la gestión de timeouts, abre el stream HTTP/2, y analiza la respuesta y los trailers de vuelta en un TsgcGRPCResponse.

La capa tipada está en la unidad sgcGRPC_Google_Storage. Te da clases de petición y respuesta como TsgcGRPCStorageListBucketsRequest, TsgcGRPCStorageListObjectsResponse y TsgcGRPCStorageBucket que se serializan a sí mismas hacia y desde Protocol Buffers. Estableces propiedades tipadas, llamas a ToBytes para obtener la carga de la petición, la envías con Call, y cargas la respuesta con LoadFromBytes. Sin compilador de protobuf, sin etiquetas de campo escritas a mano.

La autenticación usa una cuenta de servicio de Google. El cliente firma un JSON Web Token a partir de la clave de la cuenta de servicio y lo presenta como un token Bearer en la metadata authorization de gRPC, que es la forma estándar en que Google Cloud autoriza las llamadas servidor a servidor.

Configurar el transporte

Un canal gRPC no es más que una conexión HTTP/2. Apunta un TsgcHTTP2Client al endpoint de almacenamiento con TLS activado, luego adjúntalo a TsgcGRPCClient y selecciona el content type binario de 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;

Autenticación con una cuenta de servicio

Carga la clave JSON de la cuenta de servicio, configura un JWT firmado para el endpoint de almacenamiento, y coloca el token resultante en DefaultMetadata para que viaje en cada llamada. El JWT de cuenta de servicio autofirmado está vinculado a la audiencia, así que el API_Endpoint debe apuntar a storage.googleapis.com para que el token sea aceptado.

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;

Listar buckets

Para enumerar los buckets de un proyecto, rellena un TsgcGRPCStorageListBucketsRequest con el proyecto como Parent, serialízalo, y haz un Call unario al método ListBuckets. Los bytes de la respuesta se cargan directamente en un TsgcGRPCStorageListBucketsResponse, que expone cada bucket con su nombre y ubicación.

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;

Crear un bucket

Crear un bucket sigue el mismo patrón con TsgcGRPCStorageCreateBucketRequest. Establece el proyecto parent, el id del nuevo bucket, y el proyecto propietario del recurso bucket, luego llama a CreateBucket. La respuesta lleva el TsgcGRPCStorageBucket creado.

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;

Listar objetos en un bucket

Listar los objetos de un bucket usa TsgcGRPCStorageListObjectsRequest. El parent es el nombre del recurso bucket, en la forma projects/_/buckets/<bucket>. La respuesta te da el nombre, el tamaño y el content type de cada objeto, y opcionalmente puedes establecer Prefix y Delimiter para recorrer una jerarquía tipo carpeta.

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;

Leer y escribir datos de objetos

Más allá de los metadatos de bucket y objeto, la unidad también cubre las cargas de los objetos. TsgcGRPCStorageReadObjectRequest lee bytes de vuelta de un objeto, con ReadOffset y ReadLimit opcionales para descargas por rango, y la respuesta correspondiente expone los bytes Data en bruto. TsgcGRPCStorageWriteObjectRequest sube el contenido del objeto en trozos: establece el recurso WriteObjectSpec para el primer trozo, empuja Data en valores crecientes de WriteOffset, y marca FinishWrite en el último trozo. Como cada mensaje se serializa a través de ToBytes y LoadFromBytes, las mismas clases tipadas funcionan tanto si subes un archivo pequeño en una sola llamada como si transmites un objeto grande en partes.

Paginación y manejo de errores

Las respuestas de listado devuelven un NextPageToken cuando hay más resultados disponibles. Cópialo en el PageToken de la siguiente petición y vuelve a llamar para paginar buckets grandes o largos listados de objetos. En el lado de los errores, cada TsgcGRPCResponse lleva un StatusCode tipado, así que un resultado distinto de grpcOK te dice exactamente qué salió mal, desde grpcUNAUTHENTICATED para un token erróneo o ausente hasta grpcNOT_FOUND para un bucket inexistente. Para el trabajo asíncrono, OnGRPCResponse, OnGRPCError y OnGRPCException entregan la misma información a través de eventos.

Disponibilidad

El cliente gRPC de Google Cloud Storage tipado forma parte de la edición Enterprise de sgcWebSockets. Un ejemplo completo y listo para ejecutar está en Demos\21.GRPC\15.Cloud_Storage, que cubre la autenticación, el listado y la creación de buckets, y el listado de objetos. La referencia completa está en la página de producto del Cliente gRPC.

¿Preguntas o comentarios? Ponte en contacto. Recibirás respuesta de las personas que escribieron el código.