Transcodificar HTTP/JSON a gRPC

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

En esta guía se describe lo siguiente:

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

Se da por hecho que ya has completado nuestros tutoriales de gRPC y que conoces los conceptos básicos de Endpoints para APIs gRPC.

Diseñar una API compatible con la transcodificación

La transcodificación consiste en asignar solicitudes HTTP/JSON y sus parámetros a métodos gRPC y sus parámetros y tipos de retorno. Por este motivo, aunque es posible asignar una solicitud HTTP/JSON a cualquier método de API arbitrario, es útil hacerlo si la API gRPC está estructurada de forma orientada a recursos, al igual que una API REST HTTP tradicional. En otras palabras, diseñas el servicio de API de forma que utilice un pequeño número de métodos estándar, que corresponden a verbos HTTP, como GET y PUT, que operan en los recursos y las colecciones de recursos del servicio, que son en sí mismos 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 es tan fácil mapearlos.

Puedes consultar mucha más información sobre el diseño orientado a recursos y las asignaciones de transcodificación estándar en la guía de diseño de APIs. Esta guía de diseño es el estándar que se sigue en Google al diseñar APIs públicas, como las APIs de Cloud. Aunque no es necesario que sigas esta guía para usar la transcodificación de gRPC, te recomendamos que lo hagas. En concreto, las siguientes páginas pueden ayudarte a entender estos principios de diseño y a añadir asignaciones de transcodificación útiles a tus métodos:

También puede resultarte útil la siguiente página de referencia:

En el resto de este documento, usarás el ejemplo de librería que has usado en nuestros tutoriales, que ya aplica estos principios. La librería tiene colecciones de "estanterías" de recursos de "libros" que los usuarios pueden List, Get, Create o Delete.

Dónde configurar la transcodificación

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

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

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

Recibirás una lista actual de estanterías en formato JSON.

Sin embargo, es muy recomendable en términos de diseño de interfaz HTTP configurar las asignaciones de forma explícita, tal como se describe en el resto de este documento.

El estándar de configuración de la API gRPC para la configuración de servicios te permite especificar exactamente cómo se deben traducir los datos de HTTP/JSON a gRPC. Para ello, se admiten dos mecanismos: anotaciones directas en el archivo .proto y en YAML como parte del archivo de configuración de la API gRPC. Te recomendamos que uses proto anotaciones para que el código sea más fácil de leer y mantener. Para obtener más información sobre la configuración de YAML y cuándo puede ser necesario usarla, consulta Configurar la transcodificación en YAML.

A continuación, se muestra un ejemplo en el que se usa la estrategia recomendada de la librería:

// 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 a ESP que, al hacer una solicitud HTTP GET con la URL http://mydomain/v1/shelves/1, se llama al método GetShelf() del servidor gRPC, con un GetShelfRequest que contiene el ID de estantería solicitado shelf (en este caso, 1).

Añadir asignaciones de transcodificación

En esta sección se describen algunas anotaciones de asignación adicionales del ejemplo de librería. Hay dos archivos proto de ejemplo en el ejemplo de librería para que puedas implementarlo con o sin las asignaciones de transcodificación y comparar las diferencias entre los archivos proto:

Para obtener una guía más completa sobre cómo especificar asignaciones de transcodificación, consulta los métodos estándar, los métodos personalizados y la referencia de reglas HTTP.

Asignar 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 de 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 se asigna a una solicitud HTTP GET.
  • "/v1/shelves" es la plantilla de ruta de URL (añadida al dominio de tu servicio) que usa la solicitud GET para llamar a este método. La ruta de la URL también se conoce como ruta de recurso, ya que suele especificar el "elemento" o recurso que quieres usar. En este caso, todos los recursos de estantería de nuestra librería.

Por ejemplo, si un cliente llama a este método enviando un GET a la URL http://mydomain/v1/shelves, ESP llama al método gRPC ListShelves(). A continuación, el backend de gRPC devuelve los estantes, que ESP convierte al formato JSON y devuelve al cliente.

Asignar 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 de 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 se asigna a una solicitud HTTP GET.
  • "/v1/shelves/{shelf}" es la ruta de la URL de la solicitud, como antes, pero especifica /v1/shelves/ y, a continuación, {shelf}. Esta notación con llaves indica a 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 enviando un GET a la URL http://mydomain/v1/shelves/4, ESP crea un GetShelfRequest con un valor shelf de 4 y, a continuación, llama al método gRPC GetShelf() con él. A continuación, el backend de gRPC devuelve el Shelf solicitado con el ID 4, que ESP convierte al formato JSON y devuelve al cliente.

Este método solo requiere que el cliente proporcione un valor de campo de solicitud, shelf, que se especifica en la plantilla de ruta de URL con la notación de "captura" de llaves. También puede capturar varias partes de la URL si es necesario para identificar el recurso solicitado. Por ejemplo, el método GetBook requiere que el cliente especifique tanto el ID del estante como 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 literales y llaves de captura para los valores de los campos, las plantillas de ruta de URL pueden usar comodines para indicar que se debe capturar cualquier elemento de esta parte de la URL. La notación {shelf} que se usa en el ejemplo anterior es en realidad una abreviatura de {shelf=*}. Puedes consultar más información sobre las reglas de las plantillas de ruta en la referencia de reglas HTTP.

En el caso de este tipo de método, no se especifica ningún cuerpo de solicitud HTTP. Puede consultar más directrices sobre la asignación de métodos Get, incluido el uso de parámetros de consulta, en Métodos estándar.

Asignar un método Create

El método CreateShelf de Bookstore se asigna a POST de HTTP.

  // 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 se asigna a una solicitud HTTP POST.
  • "/v1/shelves" es la ruta de la URL de la solicitud, como antes.
  • body: "shelf"se usa en el cuerpo de la solicitud HTTP para especificar el recurso que quieres añadir en formato JSON.

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

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

ESP usa el cuerpo JSON para crear un valor Shelf con el tema "Music" para CreateShelfRequest y, a continuación, llama al método gRPC CreateShelf(). Ten en cuenta que el cliente no proporciona el valor id del Shelf. El servicio proporciona los IDs de las estanterías de la librería al crear una estantería. Usted proporciona este tipo de información a los usuarios de su servicio en la documentación de la API.

Usar comodines en el cuerpo

El nombre especial * se puede usar en la asignación del cuerpo para indicar que todos los campos que no estén vinculados por la plantilla de ruta deben asignarse al cuerpo de la solicitud. Esto permite 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 se asigna a una solicitud HTTP POST.
  • "/v1/shelves/{shelf_id}" es la ruta de URL de la solicitud. El valor de {shelf_id} es el valor del campo shelf_id en CreateShelfRequest.
  • body: "*" se usa en el cuerpo de la solicitud HTTP para especificar todos los campos de solicitud restantes, excepto shelf_id en este ejemplo, que son shelf_theme y shelf_size. En el caso de los campos del 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 forma:

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

ESP usa el cuerpo JSON y la plantilla de ruta para crear un CreateShelfRequest{shelf_id: 123 shelf_theme: "Music" shelf_size: 20} y, a continuación, lo usa para llamar al método CreateShelf() de gRPC. Para obtener más información, consulta HttpRule.

Configurar la transcodificación en YAML

Otra opción es especificar las asignaciones de HTTP a gRPC en el archivo YAML de configuración de la API de gRPC en lugar de en el archivo .proto. Es posible que tengas que 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 especificadas para cada servicio.

El rules de la sección http de tu archivo YAML especifica cómo asignar solicitudes HTTP/JSON a métodos 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 de cómo usar este enfoque en el servicio Bookstore de ejemplo en api_config_http.yaml.

Desplegar un servicio que usa transcodificación

Desplegar un servicio gRPC que utilice la transcodificación es muy parecido a desplegar cualquier otro servicio gRPC, con una diferencia importante. En los tutoriales, el ejemplo necesario para aceptar solicitudes gRPC del cliente de ejemplo. Sin embargo, si quieres que Bookstore también acepte solicitudes HTTP, debes realizar una configuración adicional en ESP. Los clientes usan el protocolo HTTP1.1 para enviar solicitudes JSON/HTTP al ESP, por lo que el ESP debe configurarse para usar SSL (el puerto SSL puede admitir ambos tipos de solicitudes) o debe tener habilitado un puerto especial para aceptar estas llamadas. Por lo demás, el proceso de implementación es prácticamente el mismo que en el tutorial del entorno que hayas elegido.

Asegúrate de que las reglas HTTP se hayan implementado

Si ya has descargado el ejemplo de librería para los tutoriales, ten en cuenta que debes descargar una versión ligeramente diferente del archivo .proto con anotaciones: http_bookstore.proto. También debes clonar el repositorio googleapis de GitHub antes de ejecutar protoc, ya que necesitas 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 nuevo descriptor .pb a partir de 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 para configurar las asignaciones HTTP en el archivo YAML de configuración de la API de gRPC, también debes asegurarte de que las reglas pertinentes se implementen al implementar la configuración en Endpoints. Para probarlo con el servicio Bookstore, sus reglas básicas se encuentran 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

Utilizar SSL

Si SSL está habilitado para la comunicación entre sus clientes y el ESP, los clientes pueden usar el mismo puerto para hacer llamadas gRPC o HTTP1.1. Puedes consultar cómo configurar SSL para un servicio de Endpoints en Habilitar SSL.

Especifica un puerto para que ESP acepte llamadas SSL mediante 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",
    ]

Configurar un puerto HTTP1.1

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

Llamar a un servicio mediante transcodificación

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

Configuración del servicio

Se da por hecho que ya has completado los tutoriales básicos del servicio gRPC para el entorno que hayas elegido y que 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 Bookstore habilitado para HTTP en Endpoints, tal como se describe en Asegurarse de que las reglas HTTP se han implementado.
  2. Implementa el backend y el ESP tal como se describe en el tutorial de la plataforma que hayas elegido. Para ello, usa la marca --http_port para habilitar un puerto para las solicitudes HTTP1.1:

Hacer llamadas HTTP al servicio

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

    curl http://$ESP_IP/v1/shelves
    

    o usa la misma URL con https:// si utilizas SSL. El servidor responde con lo siguiente:

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

    Si la salida muestra una respuesta binaria, comprueba la configuración del puerto, ya que es posible que estés usando el puerto HTTP2 en lugar del puerto HTTP.

  3. Prueba con otro método Create. CreateShelf requiere una clave de API, por lo que debes crear una clave para tu proyecto y definirla como $KEY. Ahora llama:

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

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