Integrando o Apache APISIX com gRPC-Web

Fei Han

January 25, 2022

Ecosystem

Introdução ao gRPC Web

Originalmente desenvolvido pelo Google, o gRPC é um framework de chamada de procedimento remoto de alto desempenho implementado sobre HTTP/2. No entanto, como os navegadores não expõem diretamente o HTTP/2, aplicações Web não podem usar o gRPC diretamente. O gRPC Web é um protocolo padronizado que resolve esse problema.

A primeira implementação do gRPC-web foi lançada em 2018 como uma biblioteca JavaScript, por meio da qual aplicações Web podem se comunicar diretamente com o serviço gRPC. O princípio é criar um pipeline gRPC de ponta a ponta compatível com HTTP/1.1 e HTTP/2. O navegador então envia uma requisição HTTP regular, e um proxy gRPC-Web localizado entre o navegador e o servidor traduz a requisição e a resposta. Semelhante ao gRPC, o gRPC Web usa um contrato pré-definido entre o cliente Web e o serviço gRPC back-end. Os Protocol Buffers são usados para serializar e codificar as mensagens.

como o gRPC-web funciona

Com o gRPC Web, os usuários podem chamar aplicações gRPC back-end diretamente usando um navegador ou cliente Node. No entanto, existem algumas limitações ao usar o gRPC-Web no lado do navegador para chamar serviços gRPC.

  • Chamadas de streaming do lado do cliente e streaming bidirecional não são suportadas.
  • Chamar serviços gRPC entre domínios requer que o CORS seja configurado no lado do servidor.
  • O lado do servidor gRPC deve ser configurado para suportar gRPC-Web, ou um serviço de proxy de terceiros deve estar disponível para traduzir a chamada entre o navegador e o servidor.

Proxy gRPC Web do Apache APISIX

O Apache APISIX suporta o proxy do protocolo gRPC Web por meio de um plugin, que realiza a conversão de protocolo e a codificação de dados quando o gRPC Web se comunica com o gRPC Server no plugin grpc-web, e seu processo de comunicação é o seguinte.

Cliente gRPC Web -> Apache APISIX (conversão de protocolo e codificação de dados) -> Servidor gRPC

A seguir, um exemplo completo mostrando como construir um Cliente gRPC Web e fazer proxy de requisições gRPC Web através do Apache APISIX. No exemplo a seguir, usaremos Go como o manipulador do servidor gRPC Server e Node como o solicitante do cliente gRPC Web.

Configurar o Protocol Buffer

O primeiro passo é instalar o compilador do Protocol Buffer e os plugins relacionados.

  1. Instale protoc e proto-grpc-*.

    O compilador do Protocol Buffer protoc e os plugins protoc-gen-go e protoc-gen-grpc-web para gerar código de interface Go, JavaScript e gRPC Web para .proto precisam ser instalados no seu sistema antes de escrever aplicações do lado do cliente e do servidor.

    Execute o seguinte script para instalar os componentes acima.

    #!/usr/bin/env bash
    
    set -ex
    
    PROTOBUF_VERSION="3.19.0"
    wget https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip
    unzip protoc-${PROTOBUF_VERSION}-linux-x86_64.zip
    mv bin/protoc /usr/local/bin/protoc
    mv include/google /usr/local/include/
    chmod +x /usr/local/bin/protoc
    
    PROTO_GO_PLUGIN_VER="1.2.0"
    wget https://github.com/grpc/grpc-go/releases/download/cmd/protoc-gen-go-grpc/v${PROTO_GO_PLUGIN_VER}/protoc-gen-go-grpc.v${PROTO_GO_PLUGIN_VER}.linux.amd64.tar.gz
    tar -zxvf protoc-gen-go-grpc.v${PROTO_GO_PLUGIN_VER}.linux.amd64.tar.gz
    mv protoc-gen-go-grpc /usr/local/bin/protoc-gen-go
    chmod +x /usr/local/bin/protoc-gen-go
    
    PROTO_JS_PLUGIN_VER="1.3.0"
    wget https://github.com/grpc/grpc-web/releases/download/${PROTO_JS_PLUGIN_VER}/protoc-gen-grpc-web-${PROTO_JS_PLUGIN_VER}-linux-x86_64
    mv protoc-gen-grpc-web-${PROTO_JS_PLUGIN_VER}-linux-x86_64 /usr/local/bin/protoc-gen-grpc-web
    chmod +x /usr/local/bin/protoc-gen-grpc-web
    
  2. Crie o arquivo proto de exemplo SayHello.

    // a6/echo.proto
    
    syntax = "proto3";
    
    package a6;
    
    option go_package = "./;a6";
    
    message EchoRequest {
    string message = 1;
    }
    
    message EchoResponse {
    string message = 1;
    }
    
    service EchoService {
    rpc Echo(EchoRequest) returns (EchoResponse);
    }
    

Configurar a aplicação do lado do servidor

  1. Gere mensagens brutas Go do lado do servidor e stubs de serviço/cliente.

    protoc -I./a6 echo.proto --go_out=plugins=grpc:./a6
    
  2. Implemente a interface do manipulador do lado do servidor.

    // a6/echo.impl.go
    
    package a6
    
    import (
    "errors"
    "golang.org/x/net/context"
    )
    
    type EchoServiceImpl struct {
    }
    
    func (esi *EchoServiceImpl) Echo(ctx context.Context, in *EchoRequest) (*EchoResponse, error) {
    if len(in.Message) <= 0 {
        return nil, errors.New("message invalid")
    }
    return &EchoResponse{Message: "response: " + in.Message}, nil
    }
    
  3. Arquivo de entrada de execução da aplicação do lado do servidor.

    // server.go
    package main
    
    import (
    "fmt"
    "log"
    "net"
    
    "apisix.apache.org/example/a6"
    "google.golang.org/grpc"
    )
    
    func main() {
    lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 50001))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    
    grpcServer := grpc.NewServer()
    a6.RegisterEchoServiceServer(grpcServer, &a6.EchoServiceImpl{})
    
    if err = grpcServer.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %s", err)
    }
    }
    
  4. Compile e inicie o serviço do lado do servidor.

    go build -o grpc-server server.go
    ./grpc-server
    

Configurar programas do cliente

  1. Gere código proto do lado do cliente.

    Gere mensagens brutas JavaScript do lado do cliente, stubs de serviço/cliente e código de interface para o JavaScript do gRPC Web.

    O plugin proto para gRPC Web fornece dois modos de geração de código.

    1. mode=grpcwebtext: O código gerado por padrão envia o payload no formato grpc-web-text.

    • Content-type: application/grpc-web-text

    • Payload usa codificação base64

    • Suporta chamadas unárias e de streaming do servidor

    1. mode=grpcweb: envia o payload no formato binário protobuf.

    • Content-type: application/grpc-web+proto
    • Payload está no formato binário protobuf
    • Atualmente, apenas chamadas unárias são suportadas
    protoc -I=./a6 echo.proto --js_out=import_style=commonjs:./a6 --grpc-web_out=import_style=commonjs,mode=grpcweb:./a6
    
  2. Instale as dependências do lado do cliente.

    npm i grpc-web
    npm i google-protobuf
    
  3. Arquivo de entrada de execução do lado do cliente.

    // client.js
    const {EchoRequest} = require('./a6/echo_pb');
    const {EchoServiceClient} = require('./a6/echo_grpc_web_pb');
    // conecte-se à entrada do Apache APISIX
    let echoService = new EchoServiceClient('http://127.0.0.1:9080');
    
    let request = new EchoRequest();
    request.setMessage("hello")
    
    echoService.echo(request, {}, function (err, response) {
       if (err) {
            console.log(err.code);
            console.log(err.message);
        } else {
            console.log(response.getMessage());
        }
    });
    
  4. Estrutura final do projeto

    $ tree .
    ├── a6
    │   ├── echo.impl.go
    │   ├── echo.pb.go
    │   ├── echo.proto
    │   ├── echo_grpc_web_pb.js
    │   └── echo_pb.js
    ├── client.js
    ├── server.go
    ├── go.mod
    ├── go.sum
    ├── package.json
    └── package-lock.json
    

Após concluir os passos acima, você configurou a aplicação do servidor gRPC Server e a aplicação do cliente gRPC Web, e iniciou a aplicação do servidor, que receberá requisições na porta 50001.

Configurar o Apache APISIX

Em seguida, basta habilitar o plugin grpc-web na configuração do plugin de roteamento do Apache APISIX para fazer proxy de requisições gRPC Web.

  1. Habilite o plugin de proxy grpc-web.

    O roteamento deve usar correspondência de prefixo (por exemplo, /* ou /grpc/example/*), porque o cliente gRPC Web passa o nome do pacote, nome da interface de serviço, nome do método, etc. declarados no proto no URI (por exemplo, /path/a6.EchoService/Echo), usar correspondência absoluta impedirá que o plugin extraia informações do proto do URI.

    $ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
    {
        "uri":"/*", // modo de correspondência de prefixo
        "plugins":{
            "grpc-web":{} // habilite o plugin gRPC Web
        },
        "upstream":{
            "scheme":"grpc",
            "type":"roundrobin",
            "nodes":{
                "127.0.0.1:50001":1 // endereços e portas de escuta do gRPC Server
            }
        }
    }'
    
  2. Valide as requisições de proxy gRPC Web.

    A requisição do protocolo gRPC Web pode ser enviada ao Apache APISIX executando client.js a partir do Node.

    A lógica de processamento do lado do cliente e do servidor acima é, respectivamente: o cliente envia uma mensagem ao servidor com o conteúdo hello, o servidor recebe a mensagem e responde com response: hello, e o resultado da execução é o seguinte.

    $ node client.js
    response: hello
    
  3. Desabilite o plugin de proxy grpc-web.

    Basta remover o atributo grpc-web da configuração do plugin de roteamento.

    $ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
    {
        "uri":"/*",
        "plugins":{
        },
        "upstream":{
            "scheme":"grpc",
            "type":"roundrobin",
            "nodes":{
                "127.0.0.1:50001":1
            }
        }
    }'
    

Resumo

Este artigo traz uma experiência prática sobre o uso do grpc-web no Apache APISIX.

Sinta-se à vontade para iniciar uma discussão no GitHub Discussions ou se comunicar via lista de e-mails.

Tags: