Como transcodificar HTTP/JSON para gRPC

O Cloud Endpoints suporta a transcodificação de protocolo para que os clientes possam acessar sua API gRPC usando HTTP/JSON. O Extensible Service Proxy (ESP) transcodifica HTTP/JSON para gRPC.

Este guia descreve:

  • como usar anotações no arquivo .proto para especificar a conversão de dados de HTTP/JSON para gRPC;
  • como implantar seu serviço no Endpoints para usar esse recurso;
  • onde encontrar mais informações de referência sobre como projetar e implementar a transcodificação para serviços gRPC.

Partimos do princípio que você já concluiu nossos tutoriais sobre gRPC e que conheça os conceitos básicos do Endpoints para APIs gRPC.

Como projetar uma API pronta para transcodificação

A transcodificação envolve o mapeamento de solicitações HTTP/JSON e os respectivos parâmetros para os métodos gRPC, além dos parâmetros e tipos de retorno delas. Por isso, embora seja possível mapear uma solicitação HTTP/JSON em qualquer método de API arbitrário, será melhor se a API gRPC for estruturada com foco nos recursos, como uma API REST HTTP (em inglês) comum. Em outras palavras, o serviço da API é criado para usar um número pequeno de métodos padrão, correspondentes a verbos HTTP, como GET e PUT. Esses métodos operam nos recursos e nos conjuntos dos recursos (que também são um tipo de recurso) do serviço. Esses métodos padrão são List, Get, Create, Update e Delete.

Se necessário, a API pode também ter alguns métodos personalizados não padrão, mas eles não são muito simples de mapear.

É possível descobrir muito mais sobre o projeto voltado para recursos e os mapeamentos de transcodificação padrão no guia de projeto da API. Esse é o guia de projeto padrão seguido no Google ao projetar APIs públicas, como APIs do Cloud. Não é preciso segui-lo para usar a transcodificação do gRPC, mas é altamente recomendável. Você entenderá melhor os princípios de projeto e aprenderá a adicionar mapeamentos de transcodificação úteis aos métodos nestas páginas:

Esta página de referência também pode ser útil:

No restante do documento, você usa o exemplo Bookstore usado em nossos tutoriais, que já usa esses princípios. Ele tem coleções com "estante" de recursos de "livros" que os usuários podem List, Get, Create ou Delete.

Onde configurar a transcodificação

A transcodificação de gRPC está ativada por padrão, e você pode usá-la sem qualquer configuração. Siga as instruções para saber como implantar um serviço usando transcodificação. O ESP enviará a mensagem de solicitação ao método do gRPC apropriado, depois que uma solicitação POST HTTP for enviada para o caminho GRPC_SERVICE_FULL_NAME/METHOD_NAME> do URL com os valores do campo de mensagem de solicitação do método (se houver) como JSON no corpo da solicitação HTTP. No exemplo anterior, GRPC_SERVICE_FULL_NAME é o nome completo do serviço gRPC e METHOD_NAME é o nome do método.

Por exemplo, ao enviar uma solicitação POST ao URL ListShelves do Bookstore da seguinte forma:

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

você receberá uma lista atual de estantes no formato JSON.

No entanto, em termos de projeto de interface HTTP, é altamente preferível a configuração explícita dos mapeamentos, conforme descrito no restante deste documento.

O padrão da API gRPC para configuração do serviço permite especificar exatamente como os dados serão convertidos de HTTP/JSON para gRPC. Há dois mecanismos que permitem fazer isso: anotações diretas no arquivo .proto e no YAML como parte do arquivo de configuração da API gRPC. É recomendado o uso de anotações proto para facilitar a leitura e a manutenção. Para mais informações sobre a configuração do YAML e quando é preciso usá-lo, consulte Como configurar a transcodificação no YAML.

Veja um exemplo com a abordagem recomendada 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;
}

Com essa anotação, o ESP é informado de que uma solicitação GET HTTP feita com o URL http://mydomain/v1/shelves/1 chamará o método GetShelf() do servidor de gRPC, com um GetShelfRequest contendo shelf do ID da estante solicitado (neste caso, 1).

Como adicionar mapeamentos de transcodificação

Nesta seção, são descritas outras anotações de mapeamento do exemplo Bookstore. Nele, há dois arquivos proto de amostra para você implantá-lo com ou sem os mapeamentos de transcodificação e comparar as diferenças nos arquivos proto:

Para um guia mais abrangente sobre como especificar mapeamentos de transcodificação, consulte Métodos padrão, Métodos personalizados e Referência de regra HTTP.

Mapear um método List

O método List é definido no arquivo .proto com o respectivo tipo de resposta:

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

A anotação em negrito especifica o mapeamento HTTP desse método.

  • option (google.api.http) especifica que esse método é uma anotação de mapeamento HTTP do gRPC.
  • get especifica que esse método é mapeado em uma solicitação HTTP GET.
  • "/v1/shelves" é o modelo de caminho de URL (anexado ao domínio do serviço) que a solicitação GET usa para chamar esse método. O caminho do URL também é conhecido como caminho do recurso, já que normalmente especifica o que ou qual recurso será usado. Neste caso, todos os recursos de estante do Bookstore.

Por exemplo, se um cliente chamar esse método enviando um GET ao URL http://mydomain/v1/shelves, o ESP chamará o método ListShelves() do gRPC. Em seguida, o back-end do gRPC retornará as estantes, que o ESP converte no formato JSON e retornará ao cliente.

Mapear um método Get

O método GetShelf do Bookstore é definido no arquivo .proto com os respectivos tipos de solicitação e resposta:

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

A anotação em negrito especifica o mapeamento HTTP desse método.

  • option (google.api.http) especifica que esse método é uma anotação de mapeamento HTTP do gRPC.
  • get especifica que esse método é mapeado em uma solicitação HTTP GET.
  • "/v1/shelves/{shelf}" é o caminho do URL para a solicitação, mas especifica /v1/shelves/ e, em seguida, {shelf}. Com essa notação entre chaves, o ESP é informado de que o que está em {shelf} é o valor a ser fornecido para shelf no parâmetro GetShelfRequest do método.

Se um cliente chamar esse método enviando uma solicitação GET ao URL http://mydomain/v1/shelves/4, o ESP criará um GetShelfRequest com um valor shelf de 4 e chamará o método GetShelf() do gRPC com ele. Em seguida, o back-end do gRPC retornará o Shelf solicitado com o ID 4, que o ESP converterá no formato JSON e retornará ao cliente.

Esse método requer apenas que um único valor do campo de solicitação seja fornecido pelo cliente, shelf, especificado no modelo de caminho do URL com a notação de "captura" entre chaves. Se necessário, também é possível capturar várias partes do URL para identificar o recurso solicitado. Por exemplo, o método GetBook precisa que o cliente especifique o ID da estante e do livro no 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;
}

Assim como os literais e as chaves de captura para valores de campo, os modelos de caminho do URL podem usar curingas para indicar que qualquer elemento nesta parte do URL precisa ser capturado. A notação {shelf} usada no exemplo anterior é, na verdade, um atalho para {shelf=*}. Saiba mais sobre as regras de modelos de caminho em Referência da regra HTTP.

Neste tipo de método, não há nenhum corpo de solicitação HTTP especificado. Encontre mais orientações sobre como mapear métodos Get, inclusive para usar parâmetros de consulta, em Métodos padrão.

Mapear um método Create

O método CreateShelf do Bookstore mapeia em 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 esse método é uma anotação de mapeamento HTTP do gRPC.
  • post especifica que esse método é mapeado em uma solicitação HTTP POST.
  • "/v1/shelves" é o caminho do URL da solicitação, conforme descrito anteriormente.
  • body: "shelf" é usado no corpo da solicitação HTTP para especificar o recurso a ser adicionado, no formato JSON.

Por exemplo, se um cliente chamou esse método assim:

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

O ESP usa o corpo JSON para criar um valor Shelf com o tema "Music" para CreateShelfRequest e, em seguida, chama o método CreateShelf() do gRPC. O cliente não fornece o valor id para o Shelf. Ao criar uma nova estante, os IDs de estante do Bookstore são fornecidos pelo serviço. Forneça esse tipo de informação aos usuários do seu serviço na documentação da API.

Usar caractere curinga no corpo

O nome especial * pode ser usado no mapeamento do corpo para indicar que cada campo não vinculado ao modelo de caminho deve ser mapeado para o corpo da solicitação. Isso permite a seguinte definição alternativa do 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 esse método é uma anotação de mapeamento HTTP do gRPC.
  • post especifica que esse método é mapeado em uma solicitação HTTP POST.
  • "/v1/shelves/{shelf_id}" é o caminho do URL da solicitação. O valor em {shelf_id} é o valor do campo shelf_id em CreateShelfRequest.
  • body: "*" é usado no corpo da solicitação HTTP para especificar todos os campos de solicitação restantes, exceto shelf_id neste exemplo, e são shelf_theme e shelf_size. Para qualquer campo no corpo JSON com esses dois nomes, os valores serão usados nos campos correspondentes de CreateShelfRequest.

Por exemplo, se um cliente chamou esse método assim:

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

O ESP usa o corpo JSON e o modelo de caminho para criar um CreateShelfRequest{shelf_id: 123 shelf_theme: "Music" shelf_size: 20} e, em seguida, usa-o para chamar o método CreateShelf() do gRPC. Para detalhes, consulte a HttpRule.

Configurar a transcodificação no YAML

Outra abordagem possível é especificar os mapeamentos de HTTP para gRPC no arquivo YAML de configuração da API gRPC, e não no arquivo .proto. Se você tiver uma única definição de API proto usada em vários serviços, com diferentes mapeamentos especificados para cada um deles, será preciso configurar a transcodificação em um arquivo YAML.

As rules na seção http do arquivo YAML especificam como mapear solicitações HTTP/JSON em 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}
  ...

Um exemplo mais completo de uso dessa abordagem para o serviço de exemplo Bookstore está em api_config_http.yaml (em inglês).

Como implantar um serviço que usa transcodificação

A implantação do serviço gRPC que usa transcodificação é semelhante à implantação de qualquer outro serviço gRPC, porém com uma importante diferença. Nos tutoriais, o exemplo precisava aceitar solicitações gRPC do cliente de amostra. No entanto, caso queira que o Bookstore também aceite solicitações HTTP, será necessário fazer outras configurações para o ESP. Os clientes usam o protocolo HTTP1.1 para enviar solicitações JSON/HTTP ao ESP. Portanto, ele precisa ser configurado para usar SSL (a porta SSL é compatível com ambos os tipos de solicitação) ou ter uma porta especial ativada para aceitar essas chamadas. Via de regra, a implantação é igual ao que consta no tutorial para o ambiente escolhido.

Garantir que regras HTTP sejam implantadas

Se você já tiver o download do exemplo do Bookstore para os Tutoriais, é preciso fazer o download de uma versão um pouco diferente do arquivo .proto com anotações, http_bookstore.proto (em inglês). Também é preciso clonar o repositório googleapis (em inglês) do GitHub antes de executar protoc, porque é necessário ter o annotations.proto (em inglês) no caminho de inclusão.

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

    GOOGLEAPIS_DIR=<your-local-googleapis-folder>

Em seguida, crie um novo descritor .pb a partir de http_bookstore.proto ao implantar a configuração no Endpoints:

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

Se estiver usando o método alternativo de configuração dos mapeamentos HTTP no arquivo YAML de configuração da API gRPC, será preciso garantir também que as regras relevantes sejam aplicadas durante a implantação da configuração no Endpoints. Para tentar fazer isso com o serviço Bookstore, as regras básicas estão no arquivo api_config.yaml (em inglês) e as regras HTTP estão no arquivo api_config_http.yaml (em inglês):

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

Como usar SSL

Caso o SSL seja ativado para comunicação entre os clientes e o ESP, os clientes poderão usar a mesma porta para fazer chamadas gRPC ou HTTP1.1. Para mais informações sobre como configurar SSL para um serviço do Endpoints, consulte Como ativar SSL.

Especifique uma porta para o ESP aceitar chamadas SSL usando a sinalização --ssl_port no arquivo de configuração do Google Kubernetes Engine (GKE) ou no 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",
    ]

Como configurar uma porta HTTP1.1

Caso não esteja usando SSL, configure uma porta separada para solicitações HTTP1.1 porque gRPC e HTTP1.1 não podem compartilhar a mesma porta sem SSL. Use a sinalização --http_port no arquivo de configuração do GKE ou o comando docker run para especificar uma porta para aceitar chamadas HTTP1.1. Caso também queira que o ESP aceite chamadas gRPC, use a sinalização --http2_port para especificar uma porta 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",
    ]

Como chamar um serviço usando a transcodificação

Nesta seção, descrevemos a configuração do serviço e como fazer chamadas HTTP para o serviço.

Configuração do serviço

Presumimos que você já tenha concluído os tutoriais básicos do serviço gRPC para o ambiente escolhido e que tenha um cluster do GKE ou uma instância do Compute Engine para executar o exemplo.

  1. Primeiramente, verifique se você já implantou a configuração do serviço Bookstore habilitado para HTTP no Endpoints, conforme descrito em Garantir que regras HTTP sejam implantadas.
  2. Implante o back-end e o ESP, como descrito no tutorial da plataforma escolhida, usando a sinalização --http_port para ativar uma porta para solicitações HTTP1.1:

Fazer chamadas HTTP ao serviço

  1. Consiga o endereço IP externo do ESP e defina-o como $ESP_IP.
  2. Faça a seguinte solicitação HTTP com curl

    curl http://$ESP_IP/v1/shelves
    

    ou utilize o mesmo URL com https://, se estiver usando SSL. O servidor responde com:

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

    Se a saída exibir uma resposta binária, verifique a configuração da porta, porque é possível que você esteja acessando a porta HTTP2 em vez da HTTP.

  3. Tente um método Create. CreateShelf requer uma Chave de API, então é preciso criar uma chave para o projeto e defini-la como $KEY. Agora, faça uma chamada para:

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

    Se você chamar GetShelves novamente, verá a nova estante.