在浏览器中访问 gRPC 消息推送服务的一些思索

gRPC 数据编码采用 Protobuf、底层传输协议使用了 HTTP/2,支持双向流( Bidirectional streaming )接口,用来开发消息推送服务的再完美不过。

由于 gRPC 的实现需要访问 HTTP/2 的底层细节(如: Trailers),而这些实现细节浏览器上是无法访问到的,因此也就注定了无法在浏览器上直接进行调用 gRPC。

HTTP/2 在 2015 年底开始被广泛支持(参考 HTTP/2 - 维基百科,自由的百科全书),另一个流行的消息推送技术 WebSocket 在 2013 年底开始被广泛支持(参考 WebSockets - Web APIs | MDN)。从 HTTP/2支持情况 页面可以看到 IE11 以下都不支持,从 WebSocket支持情况 页面可以看到 IE11 以下及 Android 4.4 以下都不支持。相对来讲 WebSocket 更成熟,而且由于 socket.io 的存在,即使在旧浏览器上也能提供服务。

现在的情况下,最好是开发一个 socket.io 代理供浏览器访问 gRPC 服务,一个 WebSocket 连接可以很方便地映射到一个 gRPC 双向流( Bidirectional streaming)接口。

下面是方案调研时接触的一些相关开源项目,供参考。

grpc-gateway

grpc-gateway is a plugin of protoc. It reads gRPC service definition, and generates a reverse-proxy server which translates a RESTful JSON API into gRPC. This server is generated according to custom options in your gRPC definition.

grpc-gateway 为 Grpc 服务生成 HTTP/1.1 JSON RESTful 反向代理服务,使得 Grpc 服务可以被不方便采用 Grpc 的客户端访问。

  • 存在的问题

    不支持双向流(Bidirectional streaming)

grpc-websocket-proxy

A proxy to transparently upgrade grpc-gateway streaming endpoints to use websockets

grpc-gateway 的 WebSocket 代理,进行了多次协议转换:

WebSocket <-> HTTP/1.1 <-> gRPC

在收到 WebSocket 连接后,会创建一个 HTTP/1.1 客户端,与 grpc-gateway 进行对接。

根据示例 Protobuf 协议文件中的描述,貌似支持 Bidirectional streaming ,但是 grpc-gateway 的 README 中明确声明了不打算支持 True bi-directional streaming ,理论上只要是基于 HTTP/1.1,就不可能实现 Bidirectional streaming

这个方案的一个优点就是 proxy 代码是根据 proto 定义自动生成的,开发效率超高。

然而运行效率低下,特别是维护大量长连接的消息推送应用,连接数要翻倍。

socket.io

Socket.IO enables real-time bidirectional event-based communication. It works on every platform, browser or device, focusing equally on reliability and speed.

node.js 下的实时双向通讯方案,优先使用 websocket 做为传输层,使用其它传输方式做为备选:ajax、jsonp 轮询,flash。

socket.io 本身已经进化成了一种规范,有各种语言下的实现。

go 语言实现 go-socket.io ,这个项目 README 中建议使用 v1.4 版本,实际使用时发现浏览器直接关闭时服务器未能检测客户端离线,未转化成 "Disconnect" 事件,使用 master 分支则没有这个问题:

go get github.com/googollee/go-socket.io

go 的 socket.io 实现没有官方支持,很不完善,go-socket.io 只有服务器端实现,已经很久没有更新了,而 go 的 socket.io 客户端实现就更不靠谱了,实际使用时很多问题,直接使用 websocket 会更靠谱一些。

envoy

envoy 有一些插件完成了部分功能:

  • gRPC HTTP/1.1 bridge

    实现了通过 HTTP/1.1 协议调用 gRPC(HTTP/2) 的功能。

    采用和 gRPC 一致的数据编码方式(Protobuf 二进制数据),只支持 unary gRPC API

  • gRPC-Web filter

    基于 gRPC Web 协议的实现,跟原始的 gRPC 协议基本一致,通过去除对 HTTP/2 底层(如 frame)的依赖,使得在浏览器上调用 gRPC 成为可能。

    当前不支持 Bidirectional Streaming ,以后在最新的浏览器中可能支持。

  • WebSocket support

    支持接入 WebSocket,会建立一条到 Upstream 的 TCP 连接,Upstream 服务需要是一个完整的 WebSocket 服务器,相当于是一个 WebSocket 的透明代理。

    由于客户端发起的 WebSocket 连接与到 Upstream 的连接(TCP)是 1:1 的关系,可扩展性较差。

Envoy 中编写插件支持 WebSocket 到 gRPC 的协议转换(支持 Bidirectional streaming )理论上是可以实现的,不过难度会比较高,需要对 Envoy 的实现非常了解。初步看了一下 envoy 的相关代码,表面上代码层次分明,但是实际上却是错综复杂,大量使用继承,上下层会互相调用,由于大量采用回调函数,逻辑不好理解。

nginx

与 envoy 的 WebSocket support 相似,支持接入 WebSocket,会建立一条到 Upstream 的 TCP 连接,Upstream 服务需要是一个完整的 WebSocket 服务器,相当于是一个 WebSocket 的透明代理,由于客户端发起的 WebSocket 连接与到 Upstream 的连接(通过 proxy_pass 指令)是 1:1 的关系,连接数倍增,参考 https://www.nginx.com/blog/http2-module-nginx/#QandAnginx

  • Nchan

    Fast, horizontally scalable, multiprocess pub/sub queuing server and proxy for HTTP, long-polling, Websockets and EventSource (SSE), powered by Nginx.

    是 Nginx 的一个插件,支持各种接入方式,是搭建消息推送系统的一种很好的选择。通过回调(HTTP/1.1 Restful)与业务系统集成在一起,使得业务系统不需要关心客户端接入细节。