Delphi에서 gRPC로 Google Cloud Storage 사용하기

· 컴포넌트

Google Cloud Storage는 Google의 객체 스토리지 서비스입니다. 데이터는 버킷에 저장되며, 각 버킷은 업로드하고, 나열하고, 읽고, 삭제하는 객체(파일)를 담고 있습니다. 대부분의 Delphi 코드는 JSON REST API를 통해 이에 접근하지만, 스토리지 서비스는 HTTP/2 위에서 Protocol Buffers로 통신하는 gRPC API도 제공합니다. 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 유닛에 있습니다. 이는 TsgcGRPCStorageListBucketsRequest, TsgcGRPCStorageListObjectsResponse, TsgcGRPCStorageBucket 같은 요청 및 응답 클래스를 제공하며, 스스로 Protocol Buffers로 직렬화하고 역직렬화합니다. 형식화된 속성을 설정하고, ToBytes를 호출하여 요청 페이로드를 얻고, Call로 전송한 다음, LoadFromBytes로 응답을 로드합니다. protobuf 컴파일러나 직접 작성한 필드 태그가 필요 없습니다.

인증은 Google 서비스 계정을 사용합니다. 클라이언트는 서비스 계정 키로 JSON 웹 토큰에 서명하여 gRPC authorization 메타데이터에 Bearer 토큰으로 제시하며, 이는 Google Cloud가 서버 간 호출을 인가하는 표준 방식입니다.

전송 설정하기

gRPC 채널은 그저 HTTP/2 연결입니다. TLS가 켜진 TsgcHTTP2Client를 스토리지 엔드포인트로 향하게 한 다음, 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는 audience에 바인딩되므로, 토큰이 수락되려면 API_Endpointstorage.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;

버킷 나열하기

프로젝트의 버킷을 열거하려면 프로젝트를 Parent로 하여 TsgcGRPCStorageListBucketsRequest를 채우고, 직렬화하고, 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까지 무엇이 잘못되었는지 정확히 알려줍니다. 비동기 작업의 경우 OnGRPCResponse, OnGRPCError, OnGRPCException이 동일한 정보를 이벤트를 통해 전달합니다.

제공 범위

형식화된 Google Cloud Storage gRPC 클라이언트는 sgcWebSockets Enterprise 에디션의 일부입니다. 인증, 버킷 나열 및 생성, 객체 나열을 다루는 완전하고 바로 실행 가능한 샘플은 Demos\21.GRPC\15.Cloud_Storage에 있습니다. 전체 레퍼런스는 gRPC Client 제품 페이지에 있습니다.

질문이나 의견이 있으신가요? 문의하기. 코드를 작성한 사람들로부터 답변을 받게 됩니다.