Transcodifica HTTP/JSON a gRPC

Cloud Endpoints admite la transcodificación de protocolo para que los clientes puedan acceder a tu API de gRPC con HTTP/JSON. El proxy de servicio extensible (ESP) transcodifica HTTP/JSON a gRPC.

Esta guía describe lo siguiente:

  • Cómo usar las anotaciones en tu archivo .proto para especificar la conversión de datos de HTTP/JSON a gRPC
  • Cómo implementar tu servicio en Endpoints para usar esta característica
  • Dónde encontrar más información de referencia sobre el diseño y la implementación de la transcodificación para los servicios de gRPC

Suponemos que ya completaste nuestros instructivos de gRPC y que estás familiarizado con los conceptos básicos de Endpoints para las API de gRPC .

Diseña una API compatible con la transcodificación

La transcodificación implica la asignación de solicitudes HTTP/JSON y sus parámetros a los métodos de gRPC y sus parámetros y tipos de datos que se muestran. Por esto, si bien es posible asignar una solicitud HTTP/JSON a cualquier método de la API arbitrario, es más fácil hacerlo si la API de gRPC se estructura de una manera orientada a los recursos, como una API de REST HTTP tradicional. En otras palabras, el servicio de API debe diseñarse para que use una cantidad pequeña de métodos estándar, en relación con verbos HTTP, como GET y PUT, que operan en los recursos del servicio y en las colecciones de recursos, que son de un tipo de recurso. Estos métodos estándar son List, Get, Create, Update y Delete.

Si es necesario, la API también puede tener algunos métodos personalizados no estándar, aunque no son tan sencillos de asignar.

Puedes obtener mucha más información sobre el diseño orientado a los recursos y asignaciones de transcodificación estándar en nuestra Guía de diseño de API. Este guía de diseño es el estándar que sigue Google cuando diseña API públicas, como las API de Cloud. Si bien no es necesario que sigas esta guía para usar la transcodificación de gRPC, te recomendamos que lo hagas. En particular, las páginas siguientes te servirán de ayuda a fin de comprender estos principios de diseño y agregar asignaciones de transcodificación útiles a tus métodos:

La página de referencia que aparece a continuación también puede ser de utilidad:

En el resto del documento, usaremos el ejemplo de Bookstore, que es el mismo de nuestros instructivos, y que ya pone en funcionamiento estos principios. Bookstore cuenta con colecciones de “biblioteca” de recursos de “libros” que los usuarios pueden List, Get, Create o Delete.

Dónde configurar la transcodificación

La transcodificación de gRPC está habilitada de forma predeterminada, y puedes usarla sin ninguna configuración. Sigue las instrucciones para implementar un servicio mediante la transcodificación. Luego, cuando envíes una solicitud POST HTTP a la ruta GRPC_SERVICE_FULL_NAME/METHOD_NAME> de la URL con los valores del campo de mensaje de la solicitud del método (si existen) como JSON en el cuerpo de la solicitud HTTP, el ESP enviará el mensaje de solicitud al método de gRPC adecuado. En el ejemplo anterior, GRPC_SERVICE_FULL_NAME es el nombre completo de tu servicio de gRPC y METHOD_NAME es el nombre del método.

Por ejemplo, si envías un POST a la URL ListShelves Bookstore de la manera siguiente :

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

Obtendrás una lista actual de las bibliotecas en formato JSON.

Sin embargo, es preferible en cuanto al diseño de interfaz HTTP que configures de forma explícita las asignaciones, como se describe en el resto de este documento.

La configuración de la API de gRPC estándar para la configuración del servicio te permite especificar con exactitud cómo se deben traducir los datos de HTTP/JSON a gRPC. Se admiten dos mecanismos para hacer esto: anotaciones directas en tu archivo .proto y en YAML como parte de tu archivo de configuración de API de gRPC. Recomendamos usar anotaciones proto para facilitar la lectura y el mantenimiento. Si quieres obtener más información sobre la configuración de YAML y cuándo es posible que debas usarla, consulta Cómo configurar la transcodificación en YAML.

Este es un ejemplo que usa el método recomendado de 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;
}

La anotación indica al ESP que realizar una solicitud HTTP GET con la URL http://mydomain/v1/shelves/1 generará una llamada al método GetShelf() del servidor de gRPC con un GetShelfRequest que contiene el ID de la biblioteca solicitada shelf (en este caso, 1).

Agrega asignaciones de transcodificación

En esta sección, se describen algunas anotaciones de asignaciones adicionales de la muestra de Bookstore. Hay dos archivos proto de muestra en la muestra de Bookstore para que puedas implementarlos con o sin las asignaciones de transcodificación y comparar las diferencias en los archivos proto:

Para consultar una guía más completa a fin de especificar las asignaciones de transcodificación, consulta Métodos estándar, Métodos personalizados y la Referencia de reglas HTTP.

Asigna un método List

El método List se define en el archivo .proto con su tipo de respuesta:

  // 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;
}

La anotación en negrita especifica la asignación HTTP para este método.

  • option (google.api.http) especifica que este método es una anotación de asignación HTTP de gRPC.
  • get especifica que este método está asignado a una solicitud GET HTTP.
  • "/v1/shelves" es la plantilla de la ruta de URL (adjunta al dominio de tu servicio) que la solicitud GET usa para llamar a este método. La ruta de URL también se conoce como la ruta del recurso, ya que suele especificar la “cosa” o recurso que quieres usar. En este caso, son todos los recursos de nuestra biblioteca de Bookstore.

Por ejemplo, si un cliente llama a este método mediante el envío de GET a la URL http://mydomain/v1/shelves, el ESP llama al método de gRPC ListShelves(). El backend de gRPC luego muestra las bibliotecas, que el ESP convierte al formato JSON y las muestra al cliente.

Asigna un método Get

El método GetShelf de Bookstore se define en el archivo .proto con sus tipos de solicitud y respuesta:

// 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;
}

La anotación en negrita especifica la asignación HTTP para este método.

  • option (google.api.http) especifica que este método es una anotación de asignación HTTP de gRPC.
  • get especifica que este método está asignado a una solicitud GET HTTP.
  • "/v1/shelves/{shelf}" es la ruta de URL para la solicitud, como antes, pero especifica /v1/shelves/ y, luego, {shelf}. Esta notación de llave indica al ESP que lo que haya en {shelf} es el valor que debe proporcionar para shelf en el parámetro GetShelfRequest del método.

Si un cliente llama a este método mediante el envío de un GET a la URL http://mydomain/v1/shelves/4, el ESP crea una GetShelfRequest con un valor shelf de 4 y, luego, llama el método de gRPC GetShelf() con ella. Luego, el backend de gRPC muestra el Shelf solicitado con el ID 4, que el ESP convierte al formato JSON y se lo muestra al cliente.

Este método solo requiere que el cliente proporcione un único valor de campo de solicitud, shelf, que especificamos en la plantilla de la ruta de URL con la notación "captura" de llave. Si es necesario, también puedes capturar varias partes de la URL para identificar el recurso solicitado. Por ejemplo, el método GetBook necesita que el cliente especifique el ID de la biblioteca y el ID del libro en la 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;
}

Además de las llaves de captura y literales para los valores de campo, las plantillas de ruta de URL pueden usar comodines a fin de indicar que se debe capturar cualquier parte de esta URL. La notación {shelf} que se usó en el ejemplo anterior es, en realidad, un acceso directo para {shelf=*}. Puedes encontrar más información sobre las reglas para las plantillas de ruta en la Referencia de reglas HTTP.

En el caso de este tipo de método, no existe un cuerpo de solicitud HTTP especificado. Puedes encontrar más lineamientos sobre la asignación de métodos Get, incluido el uso de parámetros de consulta, en Métodos estándar.

Asigna un método Create

El método CreateShelf de Bookstore asigna a 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) especifica que este método es una anotación de asignación HTTP de gRPC.
  • post especifica que este método está asignado a una solicitud POST HTTP.
  • "/v1/shelves" es la ruta de URL para la solicitud, como antes.
  • body: "shelf" se usa en el cuerpo de la solicitud HTTP a fin de especificar el recurso que quieres agregar, en formato JSON.

Por ejemplo, si un cliente llama a este método de la siguiente manera:

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

El ESP usa el cuerpo JSON a fin de crear un valor Shelf con el tema "Music" para el CreateShelfRequest y, luego, llama al método CreateShelf() de gRPC. Ten en cuenta que el cliente no proporciona el valor id para el Shelf. El servicio proporciona los ID de bibliotecas de Bookstore cuando crea una librería nueva. Este es el tipo de información que debes proporcionar a los usuarios de tu servicio en la documentación de la API.

Usa comodones en el cuerpo

El nombre especial * se puede usar en la asignación del cuerpo para indicar que cada campo que no está vinculado a la plantilla de ruta se debe asignar al cuerpo de la solicitud. Esto habilita la siguiente definición alternativa del método 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) especifica que este método es una anotación de asignación HTTP de gRPC.
  • post especifica que este método está asignado a una solicitud POST HTTP.
  • "/v1/shelves/{shelf_id}" es la ruta de URL para la solicitud. Lo que aparezca en {shelf_id} es el valor del campo shelf_id en CreateShelfRequest.
  • body: "*" se usa en el cuerpo de la solicitud HTTP a fin de especificar todos los campos de solicitud restantes, excepto shelf_id en este ejemplo, que son shelf_theme y shelf_size. Para los campos en el cuerpo JSON con estos dos nombres, sus valores se usarán en los campos correspondientes de CreateShelfRequest.

Por ejemplo, si un cliente llama a este método de la siguiente manera:

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

El ESP usa el cuerpo JSON y la plantilla de la ruta para crear un CreateShelfRequest{shelf_id: 123 shelf_theme: "Music" shelf_size: 20} y, luego, la usa a fin de llamar al método CreateShelf() de gRPC. Para obtener detalles, consulta la HttpRule.

Configura la transcodificación en YAML

Otra posibilidad es cuando especificas tus asignaciones HTTP a gRPC en el archivo YAML de la configuración de API de gRPC, en lugar del archivo .proto. Es posible que debas configurar la transcodificación en un archivo YAML si tienes una sola definición de API proto que se usa en varios servicios, con asignaciones diferentes para cada servicio.

Las rules en la sección http de tu archivo YAML especifica cómo asignar las solicitudes HTTP/JSON a los métodos de 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}
  ...

Puedes ver un ejemplo más completo sobre el uso de este método para el servicio de Bookstore de ejemplo en api_config_http.yaml.

Implementa un servicio que usa transcodificación

La implementación de un servicio de gRPC que usa la transcodificación es muy similar a la implementación de cualquier otro servicio de gRPC, con una diferencia importante. En los instructivos, el ejemplo debía aceptar las solicitudes de gRPC del cliente de muestra. Sin embargo, si quieres que Bookstore también acepte las solicitudes HTTP, debes establecer alguna configuración adicional para el ESP. Los clientes usarán el protocolo HTTP1.1 para enviar solicitudes JSON/HTTP al ESP, por lo que el ESP debe configurarse a fin de usar SSL (el puerto SSL es compatible con ambos tipos de solicitudes) o debe tener un puerto especial habilitado para aceptar estas llamadas. De lo contrario, la implementación es casi la misma que la del instructivo para el entorno elegido.

Asegúrate de que las reglas HTTP estén implementadas

Si ya descargaste el ejemplo de Bookstore para los Instructivos , ten en cuenta que debes descargar una versión algo diferente del archivo .proto con anotaciones, http_bookstore.proto. También debes clonar el repositorio googleapis de GitHub antes de ejecutar protoc, ya que deberás realizar annotations.proto en tu ruta de inclusión.

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

    GOOGLEAPIS_DIR=<your-local-googleapis-folder>

A continuación, crea un descriptor .pb nuevo desde http_bookstore.proto cuando implementes tu configuración en Endpoints:

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

Si usas el método alternativo de configuración de asignaciones HTTP en tu archivo YAML de la Configuración de la API de gRPC, también debes asegurarte de que se implementen las reglas relevantes durante la implementación de tu configuración en Endpoints. Para probarlo con el servicio de Bookstore, sus reglas básicas están en el archivo api_config.yaml y sus reglas HTTP en el archivo api_config_http.yaml:

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

Usa SSL

Si SSL está habilitado para la comunicación entre tus clientes y el ESP, los clientes pueden usar el mismo puerto a fin de realizar llamadas gRPC o HTTP1.1. Puedes obtener información sobre cómo configurar el SSL para un servicio de Endpoints en Cómo habilitar SSL.

Especifica un puerto para que el ESP acepte llamadas SSL con la marca --ssl_port en el archivo de configuración de Google Kubernetes Engine (GKE) o el comando docker run (Compute Engine/Docker).

    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",
    ]

Configura un puerto HTTP1.1

Si no usas SSL, debes configurar un puerto por separado para las solicitudes HTTP1.1, ya que gRPC y HTTP1.1 no pueden compartir el mismo puerto sin SSL. Usa la marca --http_port en el archivo de configuración GKE o el comando docker run para especificar un puerto a fin de aceptar llamadas HTTP1.1. Si también quieres que el ESP acepte llamadas de gRPC, debes usar la marca --http2_port para especificar un puerto de 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",
    ]

Llama a un servicio mediante transcodificación

En esta sección, se describe la configuración del servicio y cómo realizar llamadas HTTP al servicio.

Configuración del servicio

Suponemos que ya completaste los Instructivos básicos del servicio de gRPC del entorno seleccionado y tienes un clúster de GKE o una instancia de Compute Engine para ejecutar el ejemplo.

  1. Primero, asegúrate de haber implementado la configuración del servicio de Bookstore habilitada para HTTP en Endpoints, como se describe en Asegúrate de que las reglas HTTP estén implementadas.
  2. Implementa el backend y el ESP como se describe en el instructivo para la plataforma seleccionada con la marca --http_port a fin de habilitar un puerto destinado a solicitudes HTTP1.1:

Realiza llamadas HTTP al servicio

  1. Obtén la dirección IP externa del ESP y configúrala en $ESP_IP.
  2. Realiza la solicitud HTTP siguiente con curl

    curl http://$ESP_IP/v1/shelves
    

    (o usa la misma URL con https://, si usas SSL). El servidor debe responder con lo que se describe a continuación:

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

    Si el resultado muestra una respuesta binaria, verifica la configuración de tu puerto, ya que podrías estar usando tu puerto HTTP2, en lugar del puerto HTTP.

  3. Prueba el método Create. CreateShelf requiere una clave de API, por lo que debes crear una clave para tu proyecto y configurarla como $KEY. Ahora, realiza una llamada:

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

    Si vuelves a llamar a GetShelves, deberías ver tu biblioteca nueva.