写点什么

Apache APISIX 新技能,代理 gRPC-Web 请求

  • 2022 年 2 月 23 日
  • 本文字数:3777 字

    阅读完需:约 12 分钟

gRPC Web 背景介绍

gRPC 最初由谷歌开发,是一个基于 HTTP/2 实现的高性能远程过程调用框架。但由于浏览器没有直接暴露 HTTP/2,所以 Web 应用程序不能直接使用 gRPC。gRPC Web 是一个标准化协议,它解决了这个问题。


第一个 gRPC-web 实现是在 2018 年作为一个 JavaScript 库发布的,Web 应用程序可以通过它直接与 gRPC 服务通信。其原理是创建与 HTTP/1.1 和 HTTP/2 兼容的端到端 gRPC 管道,然后浏览器发送常规的 HTTP 请求,位于浏览器和服务器之间的 gRPC-Web 代理对请求和响应进行转换。与 gRPC 类似,gRPC Web 在 Web 客户端和后端 gRPC 服务之间使用预定义的契约。Protocol Buffers 被用来序列化和编码消息。



有了 gRPC Web,用户可以使用浏览器或 Node 客户端直接调用后端的 gRPC 应用程序。不过,在浏览器端使用 gRPC-Web 调用 gRPC 服务也存在一些限制:


  • 不支持客户端流和双向流调用。

  • 跨域调用 gRPC 服务需要在服务器端配置 CORS。

  • gRPC 服务器端必须配置为支持 gRPC-Web,或者必须有第三方服务代理在浏览器和服务器之间对调用进行转换。

Apache APISIX gRPC Web Proxy

Apache APISIX 通过插件的方式支持 gRPC Web 协议的代理,在 grpc-web 插件中完成了 gRPC Web 与 gRPC Server 通讯时的协议转换及数据编解码工作,其通讯的过程如下:


gRPC Web Client -> Apache APISIX(protocol conversion & data codec) -> gRPC server


接下来通过一个完整的示例向大家演示怎样构建一个 gRPC Web 客户端,并通过 Apache APISIX 进行 gRPC Web 请求的代理。在以下的示例中,我们会将 Go 作为 gRPC Server 服务端处理程序,Node 作为 gRPC Web 客户端请求程序。

配置 Protocol Buffer

首先进行第一步,安装 Protocol Buffer 编译器及相关插件。


  1. 安装 protocproto-grpc-* 插件。

  2. 在编写客户端和服务端程序前,需要在系统中安装 Protocol Buffer 编译器 protoc 和 用于生成 .proto 的 Go、JavaScript、gRPC Web 接口代码的 protoc-gen-goprotoc-gen-grpc-web 插件。

  3. 请运行以下脚本,安装上述组件。


   #!/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
复制代码


  1. 创建 SayHello 示例 proto 文件。


    // 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);    }
复制代码

配置服务端程序

  1. 生成服务端 Go 原始消息和服务/客户端存根。


   protoc -I./a6 echo.proto --go_out=plugins=grpc:./a6
复制代码


  1. 实现服务端处理程序接口。


   // 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    }
复制代码


  1. 服务端程序运行入口文件。


   // 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)    }    }
复制代码


  1. 编译并启动服务端服务。


   go build -o grpc-server server.go   ./grpc-server
复制代码

配置客户端程序

  1. 生成客户端 proto 代码

  2. 生成客户端 JavaScript 原始消息、服务/客户端存根和 gRPC Web 的 JavaScript 的接口代码。 gRPC Web 的 proto 插件提供了两种代码生成模式:

  3. mode=grpcwebtext: 默认生成的代码以 grpc-web-text 格式发送 payload

  4. Content-type: application/grpc-web-text

  5. Payload 使用 base64 编码

  6. 支持一元和服务器流式调用

  7. mode=grpcweb:以二进制 protobuf 格式发送 payload

  8. Content-type: application/grpc-web+proto

  9. Payload 采用二进制 protobuf 格式

  10. 目前仅支持一元调用


   $ protoc -I=./a6 echo.proto --js_out=import_style=commonjs:./a6 --grpc-web_out=import_style=commonjs,mode=grpcweb:./a6
复制代码


  1. 安装客户端依赖库


   $ npm i grpc-web   $ npm i google-protobuf
复制代码


  1. 客户端执行入口文件


   // client.js   const {EchoRequest} = require('./a6/echo_pb');   const {EchoServiceClient} = require('./a6/echo_grpc_web_pb');   // 连接到 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());        }    });
复制代码


  1. 最终项目结构


   $ 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
复制代码


完成上述的步骤之后,你已经配置了把 gRPC Server 的服务端程序和 gRPC Web 的客户端程序,并且启动了服务端程序,它将通过 50001 端口接收请求。

配置 Apache APISIX

接下来只需在 Apache APISIX 路由的插件配置中启用 grpc-web 插件,即可进行 gRPC Web 请求的代理。


  1. 启用 grpc-web 代理插件

  2. 启用 grpc-web 代理插件,路由必须使用前缀匹配模式(例如:/* 或 /grpc/example/*), 因 gRPC Web 客户端会在 URI 中传递 proto 中声明的包名称、服务接口名称、方法名称等信息(例如:/path/a6.EchoService/Echo),使用绝对匹配时会使插件无法从 URI 中提取 proto 信息


   $ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '    {        "uri":"/*", // 前缀匹配模式        "plugins":{            "grpc-web":{} //开启 gRPC Web 代理        },        "upstream":{            "scheme":"grpc",            "type":"roundrobin",            "nodes":{                "127.0.0.1:50001":1 // gRPC Server Listen 地址和端口            }        }    }'
复制代码


  1. 验证 gRPC Web 代理请求

  2. 通过 Node 执行 client.js 即可向 Apache APISIX 发送 gRPC Web 协议请求。 上述客户端和服务端的处理逻辑分别是:客户端向服务端发送一条消息内容是 hello,服务端收到消息后响应 response: hello,执行结果如下。


   $ node client.js   response: hello
复制代码


  1. 关闭 grpc-web 代理插件

  2. 只需将路由插件配置中的 grpc-web 属性移除即可。


   $ 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            }        }    }'
复制代码

总结

本文为大家带来了 Apache APISIX 的 grpc-web 插件讲解及实战案例。


欢迎随时在 GitHub Discussions 中发起讨论,或通过邮件列表进行交流。

用户头像

Github:https://github.com/apache/apisix 2021.06.02 加入

Apache APISIX 是一个云原生、高性能、可扩展的微服务 API 网关。它是基于 OpenResty 和 etcd 来实现,和传统 API 网关相比,Apache APISIX 具备动态路由和插件热加载,特别适合微服务体系下的 API 管理。

评论

发布
暂无评论
Apache APISIX 新技能,代理 gRPC-Web 请求