Google Cloud Storage su gRPC in Delphi

· Componenti

Google Cloud Storage è il servizio di object storage di Google. I dati risiedono in bucket e ogni bucket contiene oggetti (file) che carichi, elenchi, leggi ed elimini. La maggior parte del codice Delphi lo raggiunge tramite l'API REST JSON, ma il servizio di storage espone anche un'API gRPC che parla Protocol Buffers su HTTP/2. sgcWebSockets Enterprise include un client gRPC Cloud Storage tipizzato costruito su TsgcGRPCClient, così puoi gestire bucket e oggetti da Delphi e C++Builder senza alcun runtime esterno o stub generato.

Come funziona

L'API Storage gRPC risiede a storage.googleapis.com:443 e parla il servizio google.storage.v2.Storage. sgcWebSockets include già uno stack HTTP/2 completo, quindi il client tipizzato poggia sopra un trasporto TsgcHTTP2Client con TLS abilitato. TsgcGRPCClient fa l'incapsulamento gRPC, gli header e la gestione dei timeout, apre lo stream HTTP/2 e analizza la risposta e i trailer in un TsgcGRPCResponse.

Il livello tipizzato è nell'unità sgcGRPC_Google_Storage. Ti dà classi di richiesta e risposta come TsgcGRPCStorageListBucketsRequest, TsgcGRPCStorageListObjectsResponse e TsgcGRPCStorageBucket che si serializzano da e verso i Protocol Buffers. Imposti proprietà tipizzate, chiami ToBytes per ottenere il payload della richiesta, lo invii con Call e carichi la risposta con LoadFromBytes. Nessun compilatore protobuf, nessun tag di campo scritto a mano.

L'autenticazione usa un account di servizio Google. Il client firma un JSON Web Token dalla chiave dell'account di servizio e lo presenta come token Bearer nei metadata authorization gRPC, che è il modo standard con cui Google Cloud autorizza le chiamate server-to-server.

Configurare il trasporto

Un canale gRPC è semplicemente una connessione HTTP/2. Punta un TsgcHTTP2Client all'endpoint di storage con TLS attivo, poi collegalo a TsgcGRPCClient e seleziona il content type Protocol Buffers binario.

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;

Autenticarsi con un account di servizio

Carica la chiave JSON dell'account di servizio, configura un JWT firmato per l'endpoint di storage e inserisci il token risultante in DefaultMetadata in modo che viaggi su ogni chiamata. Il JWT autofirmato dell'account di servizio è vincolato all'audience, quindi API_Endpoint deve puntare a storage.googleapis.com affinché il token sia accettato.

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;

Elencare i bucket

Per enumerare i bucket in un progetto, compila un TsgcGRPCStorageListBucketsRequest con il progetto come Parent, serializzalo ed esegui una Call unaria al metodo ListBuckets. I byte della risposta si caricano direttamente in un TsgcGRPCStorageListBucketsResponse, che espone ogni bucket con il suo nome e la sua posizione.

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;

Creare un bucket

Creare un bucket segue lo stesso schema con TsgcGRPCStorageCreateBucketRequest. Imposta il progetto parent, l'id del nuovo bucket e il progetto a cui appartiene la risorsa bucket, poi chiama CreateBucket. La risposta porta con sé il TsgcGRPCStorageBucket creato.

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;

Elencare gli oggetti in un bucket

Elencare gli oggetti in un bucket usa TsgcGRPCStorageListObjectsRequest. Il parent è il nome della risorsa bucket, nella forma projects/_/buckets/<bucket>. La risposta ti dà il nome, la dimensione e il content type di ogni oggetto e puoi opzionalmente impostare Prefix e Delimiter per percorrere una gerarchia simile a cartelle.

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;

Leggere e scrivere i dati degli oggetti

Oltre ai metadata di bucket e oggetti, l'unità copre anche i payload degli oggetti. TsgcGRPCStorageReadObjectRequest legge i byte di ritorno da un oggetto, con ReadOffset e ReadLimit opzionali per download a intervalli, e la risposta corrispondente espone i byte grezzi di Data. TsgcGRPCStorageWriteObjectRequest carica il contenuto dell'oggetto a blocchi: imposta la risorsa WriteObjectSpec per il primo blocco, spingi Data a valori crescenti di WriteOffset e marca FinishWrite sull'ultimo blocco. Poiché ogni messaggio si serializza tramite ToBytes e LoadFromBytes, le stesse classi tipizzate funzionano sia che tu carichi un file piccolo in una chiamata sia che tu trasmetta a pezzi un oggetto grande.

Paginazione e gestione degli errori

Le risposte delle liste restituiscono un NextPageToken quando sono disponibili altri risultati. Copialo nel PageToken della richiesta successiva e chiama di nuovo per paginare su bucket grandi o lunghi elenchi di oggetti. Sul fronte degli errori, ogni TsgcGRPCResponse porta uno StatusCode tipizzato, quindi un risultato diverso da grpcOK ti dice esattamente cosa è andato storto, da grpcUNAUTHENTICATED per un token errato o mancante a grpcNOT_FOUND per un bucket mancante. Per il lavoro asincrono, OnGRPCResponse, OnGRPCError e OnGRPCException consegnano le stesse informazioni tramite eventi.

Disponibilità

Il client gRPC Google Cloud Storage tipizzato fa parte dell'edizione Enterprise di sgcWebSockets. Un esempio completo e pronto all'uso è in Demos\21.GRPC\15.Cloud_Storage, che copre autenticazione, elenco e creazione di bucket ed elenco di oggetti. Il riferimento completo è sulla pagina prodotto gRPC Client.

Domande o feedback? Contattaci. Riceverai una risposta dalle persone che hanno scritto il codice.