Google Cloud Storage via gRPC in Delphi

· Componenten

Google Cloud Storage is Google's object-opslagdienst. Data leeft in buckets, en elke bucket bevat objecten (bestanden) die je uploadt, opsomt, leest en verwijdert. De meeste Delphi-code bereikt het via de JSON REST-API, maar de opslagdienst biedt ook een gRPC-API die Protocol Buffers spreekt over HTTP/2. sgcWebSockets Enterprise levert een getypeerde Cloud Storage gRPC-client gebouwd op TsgcGRPCClient, zodat je buckets en objecten kunt aansturen vanuit Delphi en C++Builder zonder enige externe runtime of gegenereerde stubs.

Hoe het werkt

De gRPC Storage-API bevindt zich op storage.googleapis.com:443 en spreekt de google.storage.v2.Storage-service. sgcWebSockets levert al een complete HTTP/2-stack, dus de getypeerde client draait bovenop een TsgcHTTP2Client-transport met TLS ingeschakeld. TsgcGRPCClient doet de gRPC-framing, headers en timeout-afhandeling, opent de HTTP/2-stream, en parset de response en trailers terug naar een TsgcGRPCResponse.

De getypeerde laag zit in de sgcGRPC_Google_Storage-unit. Hij geeft je request- en response-klassen zoals TsgcGRPCStorageListBucketsRequest, TsgcGRPCStorageListObjectsResponse en TsgcGRPCStorageBucket die zichzelf serialiseren naar en van Protocol Buffers. Je stelt getypeerde eigenschappen in, roept ToBytes aan om de request-payload te krijgen, verstuurt het met Call, en laadt het antwoord met LoadFromBytes. Geen protobuf-compiler, geen handgeschreven veld-tags.

Authenticatie gebruikt een Google-service-account. De client ondertekent een JSON Web Token vanuit de service-account-sleutel en presenteert het als een Bearer-token in de gRPC authorization-metadata, wat de standaardmanier is waarop Google Cloud server-naar-server-calls autoriseert.

De transport opzetten

Een gRPC-kanaal is gewoon een HTTP/2-verbinding. Richt een TsgcHTTP2Client op het storage-endpoint met TLS aan, koppel het vervolgens aan TsgcGRPCClient en selecteer het binaire Protocol Buffers-contenttype.

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;

Authenticeren met een service-account

Laad de service-account-JSON-sleutel, configureer een JWT ondertekend voor het storage-endpoint, en plaats het resulterende token in DefaultMetadata zodat het bij elke call meereist. De zelfondertekende service-account-JWT is audience-gebonden, dus het API_Endpoint moet wijzen naar storage.googleapis.com om het token te laten accepteren.

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;

Buckets opsommen

Om de buckets in een project op te sommen, vul je een TsgcGRPCStorageListBucketsRequest met het project als Parent, serialiseer je het, en doe je een unaire Call naar de ListBuckets-methode. De antwoordbytes laden rechtstreeks in een TsgcGRPCStorageListBucketsResponse, die elke bucket met zijn naam en locatie blootlegt.

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;

Een bucket aanmaken

Een bucket aanmaken volgt hetzelfde patroon met TsgcGRPCStorageCreateBucketRequest. Stel het parent-project in, de nieuwe bucket-id, en het project dat de bucket-resource bezit, en roep vervolgens CreateBucket aan. De response bevat de aangemaakte 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;

Objecten in een bucket opsommen

Het opsommen van de objecten in een bucket gebruikt TsgcGRPCStorageListObjectsRequest. De parent is de bucket-resourcenaam, in de vorm projects/_/buckets/<bucket>. De response geeft je de naam, grootte en het contenttype van elk object, en je kunt optioneel Prefix en Delimiter instellen om door een mapachtige hiërarchie te lopen.

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;

Objectdata lezen en schrijven

Naast bucket- en objectmetadata dekt de unit ook objectpayloads. TsgcGRPCStorageReadObjectRequest leest bytes terug uit een object, met optionele ReadOffset en ReadLimit voor downloads met bereik, en de bijbehorende response legt de ruwe Data-bytes bloot. TsgcGRPCStorageWriteObjectRequest uploadt objectinhoud in chunks: stel de WriteObjectSpec-resource in voor de eerste chunk, push Data bij oplopende WriteOffset-waarden, en markeer FinishWrite op de laatste chunk. Omdat elk bericht serialiseert via ToBytes en LoadFromBytes, werken dezelfde getypeerde klassen of je nu een klein bestand in één call uploadt of een groot object in stukken streamt.

Paginering en foutafhandeling

De lijst-responses retourneren een NextPageToken wanneer er meer resultaten beschikbaar zijn. Kopieer het naar de PageToken van de volgende request en roep opnieuw aan om door grote buckets of lange objectlijsten te bladeren. Aan de foutkant draagt elke TsgcGRPCResponse een getypeerde StatusCode, dus een niet-grpcOK-resultaat vertelt je precies wat er misging, van grpcUNAUTHENTICATED voor een verkeerd of ontbrekend token tot grpcNOT_FOUND voor een ontbrekende bucket. Voor asynchroon werk leveren OnGRPCResponse, OnGRPCError en OnGRPCException dezelfde informatie via gebeurtenissen.

Beschikbaarheid

De getypeerde Google Cloud Storage gRPC-client maakt deel uit van de sgcWebSockets Enterprise-editie. Een compleet, kant-en-klaar voorbeeld staat in Demos\21.GRPC\15.Cloud_Storage, dat authenticatie, het opsommen en aanmaken van buckets, en het opsommen van objecten dekt. De volledige referentie staat op de productpagina van de gRPC Client.

Vragen of feedback? Neem contact op. Je krijgt antwoord van de mensen die de code hebben geschreven.