HTTP/JSON の gRPC へのコード変換

Cloud Endpoints はプロトコルのコード変換をサポートしており、クライアントは HTTP / JSON を使用して gRPC API にアクセスできます。HTTP / JSON から gRPC へのコード変換は Extensible Service Proxy(ESP)が行います。

このガイドでは、次の内容を説明します。

  • .proto ファイルでアノテーションを使用して、HTTP / JSON から gRPC へのデータ変換を指定する方法
  • この機能を使用するために Endpoints にサービスをデプロイする方法
  • gRPC サービス用のコード変換の設計と実装に関するリファレンス情報が見つかる場所

このドキュメントを読む前提条件として、gRPC のチュートリアルをすでに完了し、Cloud Endpoints for gRPC の API の基本コンセプトを理解している必要があります。

コード変換に適した API の設計

コード変換では、HTTP / JSON リクエストとそのパラメータを、gRPC のメソッド、そのパラメータ、戻り値の型にマッピングします。そのため、HTTP / JSON リクエストを任意の API メソッドにマッピングできます。特に、gRPC API が従来の HTTP REST API と同じようにリソース指向の方法で構造化されている場合には、この方法が役立ちます。言い換えると、サービスのリソースおよび、それ自体が一種のリソースである、リソースのコレクションに対して動作する少数の標準メソッドGETPUT のような HTTP 動詞に対応するメソッド)を使用するように、API サービスを設計する必要があります。このような標準メソッドは、ListGetCreateUpdateDelete です。

必要な場合は、標準ではないカスタム メソッドを使用することもできますが、マッピングは簡単ではありません。

リソース指向の設計と標準コード変換マッピングの詳細については、API 設計ガイドをご覧ください。このデザインガイドは、Cloud APIs のような公開 API を設計するときに Google が従う設計標準です。このガイドに従わなくても gRPC コード変換を使用することはできますが、ガイドに準拠することを強くおすすめします。これらの設計原理を理解し、役に立つコード変換マッピングをメソッドに追加するには、以下のページが特に参考になります。

以下の参照ページも役に立ちます。

以降では、チュートリアルで使用した Bookstore の例を使用します。この例では、以上の原理がすでに使用されています。Bookstore には「book」リソースの「shelf」コレクションがあり、ユーザーはこれをリスト表示(List)、取得(Get)、作成(Create)、または削除(Delete)できます。

コード変換を構成する場所

gRPC コード変換がデフォルトで有効になり、そのままの構成でこれを使用できますコード変換を使用するサービスのデプロイの手順に沿ってください。その後、メソッドのリクエスト メッセージ フィールドの値(ある場合)を HTTP リクエスト本文の JSON として使用し、HTTP POST リクエストを URL パス GRPC_SERVICE_FULL_NAME/METHOD_NAME> に送信すると、ESP はリクエスト メッセージを適切な gRPC メソッドに送信します。上記の例では、GRPC_SERVICE_FULL_NAME は gRPC サービスのフルネーム、METHOD_NAME はメソッドの名前です。

たとえば、次のようにして、POST を Bookstore の ListShelves URL に送信します。

curl -XPOST http://mydomain/endpoints.examples.bookstore.Bookstore/ListShelves

この場合は、書棚の現在のリストが JSON フォームで返されます。

ただし、後で説明するように、HTTP インターフェースの設計ではマッピングを明示的に構成することを強くおすすめします

サービス構成の gRPC API 構成標準では、HTTP / JSON から gRPC へのデータの変換方法を厳密に指定できます。そのために、.proto ファイルでの直接アノテーション、および gRPC API 構成ファイルの一部としての YAML という 2 つのメカニズムがサポートされています。読みやすさと保守の容易さの点から、proto アノテーションを使用することをおすすめします。YAML の構成の詳細と、YAML を使用する必要がある場合については、YAML でのコード変換の構成をご覧ください。

推奨される方法を使用している Bookstore の例を次に示します。

// Returns a specific bookstore shelf.
rpc GetShelf(GetShelfRequest) returns (Shelf) {
  // Client example - returns the first shelf:
  //   curl http://DOMAIN_NAME/v1/shelves/1
  option (google.api.http) = { get: "/v1/shelves/{shelf}" };
}

...
// Request message for GetShelf method.
message GetShelfRequest {
  // The ID of the shelf resource to retrieve.
  int64 shelf = 1;
}

このアノテーションでは、URL http://mydomain/v1/shelves/1 で HTTP GET リクエストが行われると、gRPC サーバーの GetShelf() メソッドを、要求された書棚 ID shelf(この場合は 1)を含む GetShelfRequest を使って呼び出すように ESP に指示しています。

コード変換マッピングの追加

このセクションでは、Bookstore サンプルのその他のマッピング アノテーションについて説明します。Bookstore サンプルには 2 つのサンプル proto ファイルがあるので、コード変換マッピングの有無にかかわらずデプロイでき、proto ファイルの違いを比較できます。

コード変換マッピングの指定に関するさらに詳細なガイドについては、標準メソッドカスタム メソッドHTTP ルール リファレンスをご覧ください。

List メソッドをマッピングする

List メソッドは、そのレスポンスのデータ型とともに .proto ファイルに定義されています。

  // Returns a list of all shelves in the bookstore.
  rpc ListShelves(google.protobuf.Empty) returns (ListShelvesResponse) {
    // Define HTTP mapping.
    // Client example (Assuming your service is hosted at the given 'DOMAIN_NAME'):
    //   curl http://DOMAIN_NAME/v1/shelves
    option (google.api.http) = { get: "/v1/shelves" };
  }
...
message ListShelvesResponse {
  // Shelves in the bookstore.
  repeated Shelf shelves = 1;
}

太字で示したアノテーションが、このメソッドの HTTP マッピングを指定しています。

  • option (google.api.http) は、このメソッドが gRPC HTTP マッピング アノテーションであることを指定します。
  • get は、このメソッドが HTTP の GET リクエストにマッピングされることを指定します。
  • "/v1/shelves" は、このメソッドを呼び出すために GET リクエストが使用する(サービスのドメインに追加される)URL パス テンプレートです。URL パスは使用する「もの」つまりリソースを通常指定するので、リソースパスとも呼ばれます。この場合は Bookstore のすべての書棚リソースです。

たとえば、クライアントが URL http://mydomain/v1/shelvesGET を送信してこのメソッドを呼び出すと、ESP は gRPC メソッド ListShelves() を呼び出します。その後、gRPC バックエンドは書棚を返し、ESP はそれを JSON 形式に変換してクライアントに返します。

Get メソッドをマッピングする

Bookstore の GetShelf メソッドは、そのリクエストとレスポンスのデータ型とともに .proto ファイルに定義されています。

// Returns a specific bookstore shelf.
rpc GetShelf(GetShelfRequest) returns (Shelf) {
  // Client example - returns the first shelf:
  //   curl http://DOMAIN_NAME/v1/shelves/1
  option (google.api.http) = { get: "/v1/shelves/{shelf}" };
}

...
// Request message for GetShelf method.
message GetShelfRequest {
  // The ID of the shelf resource to retrieve.
  int64 shelf = 1;
}
...
// A shelf resource.
message Shelf {
  // A unique shelf id.
  int64 id = 1;
  // A theme of the shelf (fiction, poetry, etc).
  string theme = 2;
}

太字で示したアノテーションが、このメソッドの HTTP マッピングを指定しています。

  • option (google.api.http) は、このメソッドが gRPC HTTP マッピング アノテーションであることを指定します。
  • get は、このメソッドが HTTP の GET リクエストにマッピングされることを指定します。
  • "/v1/shelves/{shelf}" は、前と同じようにリクエストへの URL パスですが、/v1/shelves/ を指定してから {shelf} を指定します。この中括弧の表記は、{shelf} 内の値をメソッドの GetShelfRequest パラメータで shelf に提供する必要があることを ESP に伝えます。

クライアントが GET を URL http://mydomain/v1/shelves/4 に送信することによってこのメソッドを呼び出した場合、ESP は 4 の値を shelf にして GetShelfRequest を作成した後、それを使って gRPC メソッド GetShelf() を呼び出します。gRPC バックエンドは要求された ID 4Shelf を返し、ESP はそれを JSON 形式に変換してクライアントに返します。

このメソッドの場合、クライアントが提供する必要のあるリクエスト フィールド値は shelf だけであり、これは URL パス テンプレートで中括弧の「キャプチャ」表記を使って指定されています。必要な場合は、要求されたリソースを識別するために URL の複数の部分をキャプチャすることもできます。たとえば、GetBook メソッドでは、クライアントは書棚 ID と書籍 ID を URL で指定する必要があります。

// Returns a specific book.
rpc GetBook(GetBookRequest) returns (Book) {
  // Client example - get the first book from the second shelf:
  //   curl http://DOMAIN_NAME/v1/shelves/2/books/1
  option (google.api.http) = { get: "/v1/shelves/{shelf}/books/{book}" };
}
...
// Request message for GetBook method.
message GetBookRequest {
  // The ID of the shelf from which to retrieve a book.
  int64 shelf = 1;
  // The ID of the book to retrieve.
  int64 book = 2;
}

URL パス テンプレートでは、フィールド値のリテラルとキャプチャ括弧以外にも、ワイルドカードを使用して、URL のこの部分にあるものをすべてキャプチャすべきであることを示すことができます。上記の例で使用されている {shelf} 表記は、実際は {shelf=*} のショートカットです。パス テンプレートのルールの詳細については、HTTP ルール リファレンスをご覧ください。

このメソッドタイプでは、HTTP リクエストの本文は指定されていません。クエリ パラメータの使用など、Get メソッドのマッピングに関する詳細なガイドラインについては、標準メソッドをご覧ください。

Create メソッドをマッピングする

Bookstore の CreateShelf メソッドは HTTP POST にマッピングされます。

  // Creates a new shelf in the bookstore.
  rpc CreateShelf(CreateShelfRequest) returns (Shelf) {
    // Client example:
    //   curl -d '{"theme":"Music"}' http://DOMAIN_NAME/v1/shelves
    option (google.api.http) = {
      post: "/v1/shelves"
      body: "shelf"
    };
  }
...
// Request message for CreateShelf method.
message CreateShelfRequest {
  // The shelf resource to create.
  Shelf shelf = 1;
}
...
// A shelf resource.
message Shelf {
  // A unique shelf id.
  int64 id = 1;
  // A theme of the shelf (fiction, poetry, etc).
  string theme = 2;
}
  • option (google.api.http) は、このメソッドが gRPC HTTP マッピング アノテーションであることを指定します。
  • post は、このメソッドが HTTP の POST リクエストにマッピングされることを指定します。
  • "/v1/shelves" は、前と同じようにリクエストの URL パスです。
  • body: "shelf" は、追加するリソースを JSON 形式で指定するために、HTTP リクエスト本文で使われます。

たとえば、クライアントが次のようにこのメソッドを呼び出すものとします。

curl -d '{"theme":"Music"}' http://DOMAIN_NAME/v1/shelves

ESP は JSON 本文を使用して、テーマ "Music"CreateShelfRequestShelf 値を作成してから、gRPC の CreateShelf() メソッドを呼び出します。クライアントが Shelfid 値を指定していないことに注意してください。Bookstore の書棚 ID は、新しい書棚を作成するときにサービスによって指定されます。 この種の情報は、API ドキュメントでサービスのユーザーに提供します。

本文にワイルドカードを使用する

特殊名 * を本文のマッピングで使用することで、パス テンプレートによってバインドされないすべてのフィールドがリクエスト本文にマッピングされることを示すことができます。これにより、CreateShelf メソッドの次の代替定義が可能になります。

  // Creates a new shelf in the bookstore.
  rpc CreateShelf(CreateShelfRequest) returns (Shelf) {
    // Client example:
    //   curl -d '{"shelf_theme":"Music", "shelf_size": 20}' http://DOMAIN_NAME/v1/shelves/123
    option (google.api.http) = {
      post: "/v1/shelves/{shelf_id}"
      body: "*"
    };
  }
...
// Request message for CreateShelf method.
message CreateShelfRequest {
  // A unique shelf id.
  int64 shelf_id = 1;
  // A theme of the shelf (fiction, poetry, etc).
  string shelf_theme = 2;
  // The size of the shelf
  int64 shelf_size = 3;
}
  • option (google.api.http) は、このメソッドが gRPC HTTP マッピング アノテーションであることを指定します。
  • post は、このメソッドが HTTP の POST リクエストにマッピングされることを指定します。
  • "/v1/shelves/{shelf_id}" はリクエストの URL パスです。{shelf_id} のすべてが CreateShelfRequestshelf_id フィールドの値となります。
  • HTTP リクエストの本文で body: "*" を使用すれば、以下の例のように shelf_id を除くすべての残りのリクエスト フィールド、shelf_themeshelf_size を指定できます。これら 2 つの名前を持つ JSON 本文のすべてのフィールドの値が、対応する CreateShelfRequest フィールドで使用されます。

たとえば、クライアントが次のようにこのメソッドを呼び出した場合:

curl -d '{"shelf_theme":"Music", "shelf_size": 20}' http://DOMAIN_NAME/v1/shelves/123

ESP は JSON 本文とパス テンプレートを使用して CreateShelfRequest{shelf_id: 123 shelf_theme: "Music" shelf_size: 20} を作成し、それを使用して gRPC の CreateShelf() メソッドを呼び出します。詳しくは、HttpRule をご覧ください。

YAML でのコード変換の構成

HTTP から gRPC へのマッピングを、.proto ファイルの代わりに gRPC API 構成 YAML ファイルで指定する方法もあります。1 つの proto API 定義を複数のサービスで使用していて、サービスごとにマッピングの指定が異なる場合は、コード変換を YAML で構成することが必要になる場合があります。

YAML ファイルの http セクションの rules で、HTTP / JSON リクエストを gRPC メソッドにマッピングする方法を指定します。

http:
  rules:
  ...
  #
  # 'GetShelf' is available via the GET HTTP verb and '/shelves/{shelf}' URL
  # path, where {shelf} is the value of the 'shelf' field of 'GetShelfRequest'
  # protobuf message.
  #
  # Client example - returns the first shelf:
  #   curl http://DOMAIN_NAME/v1/shelves/1
  #
  - selector: endpoints.examples.bookstore.Bookstore.GetShelf
    get: /v1/shelves/{shelf}
  ...

Bookstore サービスでこのアプローチを使用している詳細な例については、api_config_http.yaml をご覧ください。

コード変換を使用するサービスのデプロイ

コード変換を使用する gRPC サービスのデプロイは、他の gRPC サービスのデプロイとほとんど同じですが、1 つだけ大きな違いがあります。チュートリアルの例では、サンプル クライアントから gRPC リクエストを受け取ることが必要でした。一方で、Bookstore が HTTP リクエストも受け取るようにする場合は、ESP に追加の構成が必要です。クライアントは HTTP1.1 プロトコルを使用して JSON/HTTP リクエストを ESP に送信するため、ESP は SSL を使用するように構成する(SSL ポートは両方のリクエスト タイプをサポート可能)か、呼び出しを受け取るための専用のポートを有効にする必要があります。デプロイメントのそれ以外の部分は、選択した環境のチュートリアルとほぼ同じです。

デプロイされた HTTP ルールの確認

チュートリアルの Bookstore サンプルをすでにダウンロードしている場合は、アノテーションを含むわずかに異なるバージョンの .proto ファイル(http_bookstore.proto)をダウンロードする必要があります。また、インクルード パスで annotations.proto が必要になるため、protoc を実行する前に GitHub から googleapis リポジトリのクローンを作成する必要もあります。

    git clone https://github.com/googleapis/googleapis

    GOOGLEAPIS_DIR=<your-local-googleapis-folder>

次に、Endpoints に構成をデプロイするときに、http_bookstore.proto から新しい .pb 記述子を作成します。

    protoc \
        --include_imports \
        --include_source_info \
        --proto_path=${GOOGLEAPIS_DIR} \
        --proto_path=. \
        --descriptor_set_out=api_descriptor.pb \
        http_bookstore.proto

gRPC API 構成 YAML ファイルで HTTP マッピングを構成するという代替方法を使用する場合は、構成を Endpoints にデプロイするときに関連するルールがデプロイされるようにする必要もあります。Bookstore サービスでこれを試すには、その基本的なルールは api_config.yaml ファイルにあり、その HTTP ルールは api_config_http.yaml ファイルにあります。

    gcloud endpoints services deploy api_descriptor.pb api_config.yaml api_config_http.yaml

SSL の使用

クライアントと ESP の間の通信用に SSL を有効にした場合、クライアントは gRPC と HTTP1.1 の呼び出しに同じポートを使用できます。Endpoints サービス用に SSL を設定する方法については、SSL を有効にするをご覧ください。

Google Kubernetes Engine 構成ファイル(GKE)の --ssl_port フラグ、または docker run コマンド(Compute Engine/Docker)を使用して、ESP が SSL 呼び出しを受け取るポートを指定します。

    args: [
      "--http_port", "8080",
      "--ssl_port", "443",  # enable SSL port at 443 to serve https requests
      "--backend",  "grpc://127.0.0.1:8081",  # gRPC backend.
      "--service", "SERVICE_NAME",
      "--rollout_strategy", "managed",
    ]

HTTP1.1 ポートを設定する

SSL を使用しない場合、gRPC と HTTP1.1 は SSL なしで同じポートを共有できないため、HTTP1.1 リクエスト用に別のポートを設定する必要があります。GKE 構成ファイルで --http_port フラグを使用するか、docker run コマンドを使用して、HTTP1.1 呼び出しを受け取るポートを指定します。ESP が gRPC 呼び出しも受け取るようにする場合は、--http2_port フラグも使用して gRPC ポートを指定する必要があります。

    args: [
      "--http_port", "8080",  # for HTTP 1.1
      "--http2_port", "8090",  # for gRPC
      "--backend", "grpc://127.0.0.1:8081",  # gRPC backend.
      "--service", "SERVICE_NAME",
      "--rollout_strategy", "managed",
    ]

コード変換を使用するサービスの呼び出し

このセクションでは、サービスのセットアップとサービスに HTTP 呼び出しを行う方法について説明します。

サービスのセットアップ

ここでは、選択した環境に対する基本的な gRPC サービス チュートリアルを完了済みで、この例を実施するための GKE クラスタまたは Compute Engine インスタンスがあることを前提としています。

  1. 最初に、HTTP が有効な Bookstore サービスの構成を Endpoints にデプロイしてあることを確認します(デプロイされた HTTP ルールの確認をご覧ください)。
  2. 選択したプラットフォームに対応するチュートリアルの説明に従って、バックエンドと ESP をデプロイします。このとき、--http_port フラグを使用して HTTP1.1 リクエスト用のポートを有効にします。

サービスに対する HTTP 呼び出しの実行

  1. ESP の外部 IP アドレスを取得し、それを $ESP_IP に設定します。
  2. curl で次の HTTP リクエストを行います

    curl http://$ESP_IP/v1/shelves
    

    (SSL を使用している場合は https:// が付いた同じ URL を使用します)。サーバーから次のレスポンスがあります。

    {"shelves":[{"id":"1","theme":"Fiction"},{"id":"2","theme":"Fantasy"}]}
    

    出力にバイナリ レスポンスが表示される場合は、ポートの構成を確認します。HTTP ポートではなく HTTP2 ポートにアクセスしている可能性があります。

  3. Create メソッドを呼び出してみます。CreateShelf には API キーが必要なため、プロジェクトのキーを作成し、それを $KEY として設定します。次の呼び出しを行います。

    curl -d '{"theme":"Music"}' http://$ESP_IP/v1/shelves?key=$KEY
    

    GetShelves を再び呼び出すと、新しい書棚が表示されます。