SORCERERXW

使用 gRPC 与前端交互:gRPC-Gateway 与 gRPC-Web

gRPC 是服务端环境里面被广泛使用的通信协议,得益于 Protobuf IDL,我们可以快速生成 Client 和 Server Stub,大大提升开发效率,也避免了通信过程当中的类型错误问题。

那么我们是否也能够将 gRPC 使用到与前端的对接当中?答案显然是可以的,只需要客户端能够和服务端顺利建立 HTTP2 连接,就能在上面以 gRPC 协议完成数据交互。但是对于网页来说,虽然浏览器支持 HTTP/2,网页代码并不能强制指定某一个请求是走 HTTP/2 还是 HTTP/1.1,网络底层被浏览器屏蔽了,可以参考下面的这篇文章:

The state of gRPC in the browser

gRPC 1.0 was released in August 2016 and has since grown to become one of the premier technical solutions for application communications. It has been adopted by startups, enterprise companies, and open source projects worldwide. Its support for polyglot environments, focus on performance, type safety, and developer productivity has transformed the way developers design their architectures.

icon

How to implement HTTP/2 stream connection in browser?

Thanks for contributing an answer to Stack Overflow! Please be sure to answer the question. Provide details and share your research! Asking for help, clarification, or responding to other answers. Making statements based on opinion; back them up with references or personal experience. To learn more, see our tips on writing great answers.

icon

所以如果希望将 gRPC 这套机制搬到浏览器上,就需要在中间再套一层协议转换层,以实现同时支持 HTTP/1.x 和 HTTP/2。

目前比较成熟的方案有 gRPC-Gateway 和 gRPC-Web:

gRPC-Gateway

gRPC-Gateway 是由社区主导的项目,它通过解析 Protobuf IDL 当中的 google.api.http 声明,为 gRPC 生成 http+JSON gateway server。用户只需要启动这个 gateway server,它就会接收请求并将其映射到相应的 gRPC 方法,并去调用相应的服务。

rpc GetUser(GetUserRequest) returns (GetUserResponse) {
  (google.api.http) = {
    get: "/v1/user/{id}"
  }
}; 

gRPC-Web

gRPC-Web 项目是由 gRPC 官方给出的,在浏览器当中使用 gRPC 的方案。它基于以下规范,在 gRPC 和 gRPC-Web 当中做转码:

grpc/PROTOCOL-WEB.md at master · grpc/grpc

The C based gRPC (C++, Python, Ruby, Objective-C, PHP, C#) - grpc/PROTOCOL-WEB.md at master · grpc/grpc

icon

不同于 gRPC-Gateway,gRPC-Web 不需要额外 IDL 声明,它的 server 依然是直接接收 gRPC 请求,只不过为了适应 HTTP/1.x 在数据编码和传输上做了一定的调整。

gRPC-Web 在客户端与服务端之间交互的 payload 可以是二进制形式,也可以通过 BASE64 编码。

curl -v 'http://localhost:8080/api.v1.API/GetInfo' \
  -H 'accept: application/grpc-web-text' \
  -H 'x-grpc-web: 1' \
  -H 'content-type: application/grpc-web-text' \
  --data-raw 'AAAAAD4KPGh0dHBzOi8vdHdpdHRlci5jb20vY29vbFhpYW8vc3RhdHVzLzE0ODIyNTQ3MjU3ODk1NDQ0NDg/cz0yMA==' \
  --compressed

*   Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> POST /api.v1.API/GetInfo HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept-Encoding: deflate, gzip
> accept: application/grpc-web-text
> x-grpc-web: 1
> content-type: application/grpc-web-text
> Content-Length: 92
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Access-Control-Expose-Headers: Content-Type, Vary, Date, grpc-status, grpc-message
< Content-Type: application/grpc-web-text
< Vary: Origin
< Transfer-Encoding: chunked
<
* Connection #0 to host localhost left intact
AAAAARSKAZACCgpoYXBweSB4aWFvEglAY29vbFhpYW8aRWh0dHBzOi8vcGJzLnR3aW1nLmNvbS9wcm9maWxlX2ltYWdlcy8xMzk4MTQ1NTE0NjY3ODQzNTg2Ly1rNHRheHJMLmpwZyKnAei/meS4que9keermeaPkOS+m+WFjei0ueeahCBtYWNPUyDlupTnlKjlm77moIcKCmh0dHBzOi8vdC5jby9Wd0p2RWVPQndnCgrmiJHmkJzkuobkuIDkuIsgT2JzaWRpYW4g5ZKMIE5vdGlvbgrmnInkupvnmoTnoa7mr5Tljp/phY3nmoTlpb3nnIvwn5mCIGh0dHBzOi8vdC5jby83a1lLSXozTzg3KgYI6PGJjwY=gAAAABBncnBjLXN0YXR1czogMA0K%

真是因为直接传递二进制的原因,会导致我们在开发的时候难以调试,光光通过抓包无法理解 payload 当中的内容,一定程度上降低了开发的效率。

不过因为编解码 payload 都强依赖 proto,也在很大程度上避免自己的接口被他人直接使用。使用下面这个工具可以直接查看 proto 内容,不过无法知道各个字段的意义,聊胜于无。

Protobuf Decoder

在 HTTP/1.x 实现 Streaming

不同于 HTTP/2 是一个全双工的网络协议,在 HTTP/1.x 当中服务端并不能主动推送数据给客户端。所以,有两种方式可以实现:

Websocket

服务接收到请求的时候,直接和客户端协商升级协议到 Websocket,之后就可以自由地传输数据了。

Payload Chunk

在一个 response 下发多条记录,记录之间使用 boundary 隔开,这样就可以模拟出服务端流了。

就如我之前介绍的 SSE 也是通过这种方式下发数据,只是通过统一的标准,浏览器在上层又做了一层封装。

在 gRPC-Gateway 和 gRPC-Web 当中,为了实现 Server Streaming,它们都使用了 Transfer Encoding Chunk 的方案,他们会在 response 加上 header Transfer-Encoding: chunked ,这样客户端就会根据 boundary 分片接收服务端的数据了。

总结

gRPC-GatewaygRPC-Web
Unary
Server Streaming⚠️(需要使用 Base64 编码)
Client Streaming⚠️(需要使用转发模式)
Bi-directional Streaming
默认编码JSONProtocol Buffers + Base64
场景同时支持 rest 和 grpc 两套接口,使既有系统可以从 rest 平滑迁移到 grpc前后端直接使用 gRPC 协议交互,使用 gRPC-Web 为 HTTP/1.x 提供支持