HTTP/JSON을 gRPC로 트랜스코딩

Cloud Endpoints는 클라이언트가 HTTP/JSON을 사용하여 gRPC API에 액세스할 수 있도록 프로토콜 트랜스코딩을 지원합니다. Extensible Service Proxy(ESP)는 HTTP/JSON을 gRPC로 트랜스코딩합니다.

이 가이드에서는 다음을 설명합니다.

  • .proto 파일에서 주석을 사용하여 HTTP/JSON에서 gRPC로의 데이터 변환을 지정하는 방법
  • 이 기능을 사용하도록 Endpoints에서 서비스를 배포하는 방법
  • gRPC 서비스의 트랜스코딩 디자인 및 구현에 대한 추가 참조 정보 위치

단, gRPC 가이드를 이미 마치고 기본적인 gRPC API용 Endpoints 개념에 익숙하다는 가정을 전제로 합니다.

트랜스코딩 친화적인 API 설계

트랜스코딩은 HTTP/JSON 요청과 요청 매개변수를 gRPC 메소드와 메소드 매개변수, 그리고 반환 유형으로 매핑하는 작업이 수반됩니다. 이러한 이유로 HTTP/JSON 요청을 임의의 API 메서드에 매핑할 수 있지만 gRPC API가 일반적인 HTTPREST API처럼 리소스 지향적인 방식으로 정형화되어 있으면 매핑하는 데 더욱 유용합니다. 다시 말해서 API 서비스를 설계할 때는 HTTP 동사(GETPUT) 같이 서비스의 리소스와 자체로 리소스 유형인 리소스의 컬렉션에서 실행되는 표준 메서드를 사용하도록 설계하세요. 이러한 표준 메서드는 List, Get, Create, Update, Delete입니다.

필요에 따라 API에 표준이 아닌 커스텀 메서드도 포함될 수 있지만 이러한 메서드는 매핑 시 직관적이지 않습니다.

리소스 지향적인 설계와 표준 트랜스코딩 매핑에 대한 자세한 내용은 API 설계 가이드에서 확인할 수 있습니다. 이 설계 가이드는 Google에서 Cloud API와 같은 공개 API를 설계할 때 따르는 설계 표준입니다. gRPC 트랜스코딩을 사용하기 위해 이 가이드를 따를 필요는 없지만 따르는 것이 좋습니다. 특히, 다음 페이지에서는 이러한 설계 원칙을 이해하고 자신의 메서드에 유용한 트랜스코딩 매핑을 추가할 수 있습니다.

다음 참조 페이지도 유용할 수 있습니다.

이 문서의 나머지 부분에서는 가이드에서 사용했던 Bookstore 예시를 사용합니다. Bookstore 예시는 이미 이러한 규칙을 따르고 있습니다. Bookstore에는 '도서' 리소스로 구성된 '서가' 컬렉션이 있어서 사용자가 여기에서 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의 직접 주석입니다. 읽기 및 유지보수의 편의를 위해 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 요청을 하면 요청된 서가 ID shelf(여기에서는 1)와 함께 gRPC 서버의 GetShelfRequestGetShelf() 메서드가 호출된다고 ESP에게 알려줍니다.

트랜스코딩 매핑 추가

이 섹션에서는 Bookstore 샘플의 몇 가지 추가 매핑 주석에 대해 설명합니다. Bookstore 샘플에는 proto 파일 샘플이 2개 있기 때문에 트랜스코딩 매핑을 사용하거나 사용하지 않고 둘 다 배포한 후 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의 모든 서가 리소스가 여기에 해당합니다.

예를 들어 클라이언트가 GET를 URL http://mydomain/v1/shelves에 전송하여 이 메서드를 호출하면 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는 shelf 값이 4GetShelfRequest를 만든 후 이를 사용하여 gRPC 메서드 GetShelf()를 호출합니다. 그러면 gRPC 백엔드가 ID 4로 요청된 Shelf를 반환하고, ESP가 이를 JSON 형식으로 변환하여 클라이언트에 반환합니다.

이 메서드에는 클라이언트가 제공하는 단일 요청 필드 값 shelf만 필요합니다. 이 값은 URL 경로 템플릿에서 중괄호 '캡처' 표기를 사용해 지정합니다. 또한 필요에 따라 요청된 리소스 식별을 위해 URL의 여러 부분을 캡처할 수도 있습니다. 예를 들어 GetBook 메서드는 클라이언트가 URL에서 서가 ID와 서적 ID를 모두 지정해야 합니다.

// 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"는 HTTP 요청 본문에서 추가하려는 리소스를 JSON 형식으로 지정하는 데 사용됩니다.

따라서 다음과 같이 메서드를 클라이언트가 호출한 경우,

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

ESP는 JSON 본문을 사용하여 CreateShelfRequest에 대해 "Music"이 테마인 Shelf 값을 만든 다음 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 필드 값입니다.
  • body: "*"은 이 예시에서 shelf_id를 제외한 나머지 모든 요청 필드를 지정하기 위해 HTTP 요청 본문에서 사용되며 대상은 shelf_themeshelf_size입니다. 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에서 트랜스코딩 구성

다른 방법은 .proto 파일이 아닌 gRPC API 구성 YAML 파일에서 HTTP-gRPC 매핑을 지정하는 것입니다. 서비스마다 지정된 매핑이 다른 여러 서비스에서 단일 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 서비스를 배포하는 것과 상당히 비슷하지만, 한 가지 중요한 차이점이 있습니다. 가이드의 예시는 샘플 클라이언트에서 gRPC 요청을 수신해야 했습니다. 하지만 Bookstore가 HTTP 요청도 수신하도록 하려면 ESP에 추가 구성이 필요합니다. 클라이언트는 HTTP1.1 프로토콜을 사용하여 JSON/HTTP 요청을 ESP에 전송합니다. 따라서 SSL을 사용하도록 ESP를 구성하거나(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) 또는 docker run 명령어(Compute Engine/Docker)에서 --ssl_port 플래그를 사용하여 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 없이는 gPPC와 HTTP1.1이 동일한 포트를 공유할 수 없기 때문에 SSL을 사용하지 않는 경우에는 HTTP1.1에 별도의 포트를 지정해야 합니다. GKE 구성 파일 또는 docker run 명령어에서 --http_port 플래그를 사용하여 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 규칙이 배포되었는지 확인에서 설명한 대로 HTTP 기반 Bookstore 서비스 구성이 Endpoints에 배포되었는지 확인합니다.
  2. 선택한 플랫폼의 가이드에 설명된 대로 --http_port 플래그로 HTTP1.1 요청을 위한 포트를 사용 설정하여 백엔드와 ESP를 배포합니다.

서비스에 대한 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를 다시 호출하면 새 서가가 표시됩니다.