Hướng dẫn kết nối API của Google Cloud bằng giao thức RPC

Trong bài viết này, chúng ta sẽ điểm qua từng bước một về 2 phương pháp gRPC client tiêu chuẩn và gRPC fallback, sử dụng giao thức RPC nhằm mục đích gọi Cloud API tương tác với các dịch vụ của Google.

Như hầu hết những nền tảng đám mây, Google Cloud Platform (GCP) cung cấp giao diện lập trình ứng dụng được đặt tên là Cloud APIs để tương tác với những dịch vụ mà GCP cung cấp. Những Cloud APIs này cung cấp giao diện REST (REST interface), cho phép chúng ta có thể gọi trực tiếp thông qua giao thức HTTP, hoặc là dùng thư viện được cung cấp bởi GCP. Lập trình viên chúng ta thường vẫn hay sử dụng giao diện REST này. Nhưng bạn đã bao giờ nghe nói về gRPC chưa? Rất nhiều Cloud APIs có thể được sử dụng với gRPC. Nghĩa là ngoài việc cung cấp giao diện REST, Cloud APIs còn cung cấp cả giao diện gRPC (gRPC interface). So với việc dùng REST, dùng gRPC sẽ giúp ứng dụng cải thiện hiệu suất được nhiều hơn.

Trong bài viết này, chúng ta sẽ điểm qua từng bước một về 2 phương pháp khác nhau để sử dụng giao thức RPC nhằm mục đích gọi Cloud API tương tác với các dịch vụ của Google.

Đầu tiên, chúng ta sẽ xem cách tiêu chuẩn để sử dụng gRPC. Sau đó, chúng ta sẽ tìm hiểu cách sử dụng gRPC fallback, một cơ chế độc quyền của GCP. gRPC fallback thật sự là một tính năng thú vị. Hy vọng cơ chế này sẽ giúp các bạn tiết kiệm thời gian khi sử dụng Google Cloud API thông qua RPC.

Tóm tắt gRPC

Trước khi bắt đầu với gRPC, nếu bạn vẫn chưa biết về từ khóa này, cũng như cách gRPC giúp cải thiện hiệu suất so với REST, hãy xem qua những bài viết bên dưới để có cái nhìn tổng quan:

Tóm nhanh giúp bạn: Grpc được chứng minh là nhanh hơn 10 lần so với REST, và đó cũng là lí do chúng ta muốn sử dụng gRPC hơn là REST.

gRPC client tiêu chuẩn

Trong phần này, chúng ta sẽ sử dụng gRPC client tiêu chuẩn với stub và service được tạo ra từ tập tin protobuf.

Bước 1: Xác thực

Chúng ta cần xác thực khi gửi yêu cầu (request) để có thể dùng API của GCP (để biết thêm, bạn hãy xem bài viết Google Cloud Authentication Overview). Trong phần này, chúng ta sẽ sử dụng tài khoản dịch vụ (service account) để xác thực.

Chúng ta có thể tạo tài khoản dịch vụ trên trang Google Cloud Console > Service Accounts:

Cấp cho tài khoản dịch vụ vai trò Project Viewer (xem dự án):

Cuối cùng, tạo một khóa tài khoản dịch vụ (service account key):

Lưu lại tập tin khóa (dưới dạng json) thành tập tin “service-account.json”, chúng ta sẽ sử dụng trong các phần kế tiếp.

Bước 2: Lấy định nghĩa Protocol Buffers

Để tạo ra mã nguồn cho client để sử dụng một API, đầu tiên, chúng ta cần lấy định nghĩa Protocol Buffers (protobuf) của API đó. Những định nghĩa này được cung cấp trên GitHub googleapis/googleapis.

Trong ví dụ này, chúng ta sẽ sử dụng Cloud Billing Catalog API, được định nghĩa bởi cloud/billing/v1/cloud_catalog.proto. Chúng ta sẽ gọi API ListSkus. API này có đầu vào là id dịch vụ đám mây (cloud service id) và đầu ra là tất cả SKU (stock-keeping unit) của dịch vụ đám mây đó.

Bước 3: Tạo mã nguồn client

Theo các bước của bài viết Quickstart và chúng ta sẽ có được tập tin cloud_catalog.pb.go và tập tin cloud_catalog_grpc.pb.go.

Bạn sẽ cần một vài thư viện từ googleapis/googleapis. Nếu bạn chưa có kinh nghiệm với gRPC, hãy nhân bản (clone) toàn bộ kho mã nguồn (repository) về máy, sau đó chạy câu lệnh protoc trên thư mục máy bạn.

Bước 4: Tạo yêu cầu (request)

package main

import (
   pb "GCPApisRPC/billing"
   "context"
   "crypto/x509"
   "google.golang.org/grpc"
   "google.golang.org/grpc/credentials"
   "google.golang.org/grpc/credentials/oauth"
   "log"
)

func main() {
   pool, _ := x509.SystemCertPool()
   creds := credentials.NewClientTLSFromCert(pool, "")
   perRPC, _ := oauth.NewServiceAccountFromFile("service-account.json", "https://www.googleapis.com/auth/cloud-billing.readonly")
   conn, err := grpc.Dial("cloudbilling.googleapis.com:443",
      grpc.WithTransportCredentials(creds),
      grpc.WithPerRPCCredentials(perRPC))
   if err != nil {
      panic(err)
   }
   defer conn.Close()
   client := pb.NewCloudCatalogClient(conn)

   request := pb.ListSkusRequest{
      Parent: "services/CCD8-9BF1-090E",
   }
   skus, err := client.ListSkus(context.Background(), &request)
   if err != nil {
      panic(err)
   }
   log.Println(skus)
}

Và chúng ta sẽ có phản hồi (response) như sau:

gRPC Fallback

Đây là một tính năng thú vị của Cloud APIs, tính năng này cung cấp một cách khác để sử dụng gRPC: một giao thức fallback đơn giản sử dụng protobuf thông qua HTTP (nghĩa là bạn có thể dùng HTTP client tiêu chuẩn để gọi). Chỉ cần một yêu cầu HTTP bằng phương thức POST và GCP sẽ làm hộ bạn những phần sau đó.

Bước 1: Xác thực

Cũng như phần trước, chúng ta cần xác thực yêu cầu. Lần này, chúng ta sẽ sử dụng khóa API (API key) để xác thực.

Chúng ta cả thể tạo tài khoản dịch vụ trên trang Google Cloud Console > Credentials:

Bước 2: Xây dựng đường dẫn

Định dạng của một đường dẫn RPC như sau:

{Đường dẫn gốc} / {Dịch vụ} / {Phương thức}

Đường dẫn gốc: Đường dẫn được công bốc bởi chủ nhân của dịch vụ, thường kết thúc bằng “/$grpc”

Dịch vụ: Tên dịch vụ protobuf đầy đủ và đủ điều kiện (fully qualified protobuf service name) (PACKAGE_NAME + SERVICE_NAME)

Phương thức: Tên của protobuf RPC

Ví dụ, Cloud Billing Catalog API có đường dẫn RPC như sau:

cloudbilling.googleapis.com/$rpc/google.cloud.billing.v1.CloudCatalog/ListSkus

Trong đó, chúng ta có:

  • Đường dẫn gốc: cloudbilling.googleapis.com/$rpc
  • Dịch vụ: google.cloud.billing.v1.CloudCatalog
  • Phương thức: ListSkus

Chúng ta có thể dễ dàng tìm những nguyên tốc này trong định nghĩa của protobuf:

Bước 3: Tạo yêu cầu

Yêu cầu của chúng ta cần có những tiêu đề (header) như sau:

Content-Type: application/x-protobuf

Tiêu đề này là cần thiết để kích hoạt gRPC fallback

X-Google-Api-key: tiêu đề xác thực, trong trường hợp chúng ta sử dụng API key để xác thực

Authorization: tiêu đề xác thực, trong trường hợp chúng ta sử dụng OAuth2 để xác thực

Chúng ta hãy tạo một yêu cầu như minh họa bên dưới:

package main

import (
   "bytes"
   "github.com/golang/protobuf/proto"
   "google.golang.org/genproto/googleapis/cloud/billing/v1"
   "google.golang.org/genproto/googleapis/rpc/status"
   "io/ioutil"
   "log"
   "net/http"
   "os"
)

func main() {
   request := &billing.ListSkusRequest{
      Parent: "services/CCD8-9BF1-090E",
   }
   data, err := proto.Marshal(request)
   if err != nil {
      panic(err)
   }

   url := "https://cloudbilling.googleapis.com/$rpc/" +
      "google.cloud.billing.v1.CloudCatalog/ListSkus"
   req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
   if err != nil {
      panic(err)
   }
   req.Header.Set("X-Goog-Api-Key", "")
   req.Header.Set("Content-Type", "application/x-protobuf")

   client := &http.Client{}
   res, err := client.Do(req)
   if err != nil {
      panic(err)
   }
   defer res.Body.Close()
   data, err = ioutil.ReadAll(res.Body)
   if err != nil {
      panic(err)
   }

   response := &billing.ListSkusResponse{}
   if res.StatusCode == 200 {
      if err := proto.Unmarshal(data, response); err != nil {
         panic(err)
      }
   } else {
      var st status.Status
      if err := proto.Unmarshal(data, &st); err != nil {
         panic(err)
      }
      log.Printf("%+v", res.StatusCode)
      log.Printf("%+v", st)
      os.Exit(-1)
   }

   log.Printf("REQUEST: %+v", request)
   log.Printf("RESPONSE: %+v", response)
}

Trong ví dụ, phần thân của yêu cầu (request body) được chuyển đổi như bên dưới:

    request := &billing.ListSkusRequest{
        Parent: "services/CCD8-9BF1-090E",
       }
       data, err := proto.Marshal(request)

Sau đó chỉ cần đặt phần được chuyển đổi vào thân của một yêu cầu HTTP POST bình thường:

req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))

Nếu yêu cầu thành công, mã trạng thái 200 sẽ được trả về. Phần thân của phản hồi (response) sẽ chứa nội dung, chúng ta sẽ chuyển đổi nội dung này để xem được như bên dưới:

proto.Unmarshal(data, response)

Và sau đó chúng ta có thể đọc được phần phản hồi:

Trong trường hợp không thành công, chúng ta sẽ nhận mã trạng thái theo như google.rpc.Code, và phần phản hồi sẽ chứa google.rpc.Status (Xem Erros để biết thêm thông tin)

Ví dụ, chúng ta để API key trống và có phản hồi với mã trạng thái 403:

Kết luận

Thông qua bài viết này, hy vọng các bạn có thể dễ dàng gọi API của GCP bằng RPC. Với hiệu suất đã được chứng minh của RPC so với REST, có thể tin rằng trong tương lai, không chỉ GCP, mà hầu hết những nền tảng đám mây khác cũng sẽ cho phép dùng RPC để gọi API. Khi đó, chúng ta sẽ lại có những bài viết về cách dùng API của họ nhé.

Cảm ơn bạn đã dành thời gian theo dõi bài viết này.

Credits:

https://googleapis.github.io/HowToRPC.html

https://medium.com/@EmperorRXF/evaluating-performance-of-rest-vs-grpc-1b8bdf0b22da#:~:text=gRPC%20is%20roughly%207%20times,of%20HTTP%2F2%20by%20gRPC.

https://code.tutsplus.com/tutorials/rest-vs-grpc-battle-of-the-apis--cms-3071

Atekco - Home for Authentic Technical Consultants