Google Cloud Storage sobre gRPC no Delphi

· Componentes

O Google Cloud Storage é o serviço de armazenamento de objetos do Google. Os dados vivem em buckets, e cada bucket contém objetos (arquivos) que você envia, lista, lê e exclui. A maior parte do código Delphi o alcança através da API REST JSON, mas o serviço de storage também expõe uma API gRPC que fala Protocol Buffers sobre HTTP/2. O sgcWebSockets Enterprise inclui um cliente Cloud Storage gRPC tipado construído sobre o TsgcGRPCClient, para que você possa gerenciar buckets e objetos a partir do Delphi e do C++Builder sem nenhum runtime externo ou stubs gerados.

Como funciona

A API gRPC de Storage fica em storage.googleapis.com:443 e fala o serviço google.storage.v2.Storage. O sgcWebSockets já vem com uma pilha HTTP/2 completa, então o cliente tipado se apoia sobre um transporte TsgcHTTP2Client com TLS habilitado. O TsgcGRPCClient faz o enquadramento gRPC, os cabeçalhos e o tratamento de timeout, abre o stream HTTP/2 e analisa a resposta e os trailers de volta em um TsgcGRPCResponse.

A camada tipada está na unidade sgcGRPC_Google_Storage. Ela fornece classes de requisição e resposta como TsgcGRPCStorageListBucketsRequest, TsgcGRPCStorageListObjectsResponse e TsgcGRPCStorageBucket que se serializam de e para Protocol Buffers. Você define propriedades tipadas, chama ToBytes para obter o payload da requisição, o envia com Call e carrega a resposta com LoadFromBytes. Sem compilador protobuf, sem tags de campo escritas à mão.

A autenticação usa uma conta de serviço do Google. O cliente assina um JSON Web Token a partir da chave da conta de serviço e o apresenta como um token Bearer no metadado authorization do gRPC, que é a maneira padrão pela qual o Google Cloud autoriza chamadas servidor a servidor.

Configurando o transporte

Um canal gRPC é apenas uma conexão HTTP/2. Aponte um TsgcHTTP2Client para o endpoint de storage com TLS ligado, então anexe-o ao TsgcGRPCClient e selecione o tipo de conteúdo Protocol Buffers binário.

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;

Autenticando com uma conta de serviço

Carregue a chave JSON da conta de serviço, configure um JWT assinado para o endpoint de storage e coloque o token resultante no DefaultMetadata para que ele acompanhe cada chamada. O JWT autoassinado de conta de serviço é vinculado à audiência, então o API_Endpoint precisa apontar para storage.googleapis.com para que o token seja aceito.

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;

Listando buckets

Para enumerar os buckets de um projeto, preencha um TsgcGRPCStorageListBucketsRequest com o projeto como Parent, serialize-o e faça um Call unário ao método ListBuckets. Os bytes da resposta carregam diretamente em um TsgcGRPCStorageListBucketsResponse, que expõe cada bucket com seu nome e localização.

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;

Criando um bucket

Criar um bucket segue o mesmo padrão com TsgcGRPCStorageCreateBucketRequest. Defina o projeto parent, o id do novo bucket e o projeto que é dono do recurso bucket, então chame CreateBucket. A resposta carrega o TsgcGRPCStorageBucket criado.

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;

Listando objetos em um bucket

Listar os objetos em um bucket usa TsgcGRPCStorageListObjectsRequest. O parent é o nome do recurso do bucket, na forma projects/_/buckets/<bucket>. A resposta fornece o nome, o tamanho e o tipo de conteúdo de cada objeto, e você pode opcionalmente definir Prefix e Delimiter para percorrer uma hierarquia parecida com pastas.

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;

Lendo e escrevendo dados de objeto

Além dos metadados de bucket e objeto, a unidade também cobre os payloads de objetos. O TsgcGRPCStorageReadObjectRequest lê bytes de volta de um objeto, com ReadOffset e ReadLimit opcionais para downloads por faixa, e a resposta correspondente expõe os bytes brutos de Data. O TsgcGRPCStorageWriteObjectRequest envia o conteúdo do objeto em pedaços: defina o recurso WriteObjectSpec para o primeiro pedaço, envie Data em valores crescentes de WriteOffset e marque FinishWrite no último pedaço. Como cada mensagem serializa através de ToBytes e LoadFromBytes, as mesmas classes tipadas funcionam quer você envie um arquivo pequeno em uma chamada ou faça streaming de um objeto grande em partes.

Paginação e tratamento de erros

As respostas de listagem retornam um NextPageToken quando há mais resultados disponíveis. Copie-o para o PageToken da próxima requisição e chame novamente para paginar por buckets grandes ou longas listagens de objetos. No lado do erro, cada TsgcGRPCResponse carrega um StatusCode tipado, então um resultado diferente de grpcOK diz exatamente o que deu errado, de grpcUNAUTHENTICATED para um token inválido ou ausente até grpcNOT_FOUND para um bucket inexistente. Para trabalho assíncrono, OnGRPCResponse, OnGRPCError e OnGRPCException entregam a mesma informação através de eventos.

Disponibilidade

O cliente Google Cloud Storage gRPC tipado faz parte da edição Enterprise do sgcWebSockets. Um exemplo completo e pronto para executar está em Demos\21.GRPC\15.Cloud_Storage, cobrindo autenticação, listagem e criação de buckets, e listagem de objetos. A referência completa está na página do produto gRPC Client.

Dúvidas ou comentários? Entre em contato. Você receberá uma resposta das pessoas que escreveram o código.