grpc-web不能直接用grpc-web包发请求,因浏览器不支持http/2明文,需通过http/1.1 post发送base64编码的protobuf,依赖代理(如envoy)做协议转换,前端须用@grpc/grpc-web客户端而非@grpc/grpc-js。

gRPC-Web 为什么不能直接用 grpc-web 包发请求?
因为浏览器不支持 HTTP/2 明文(h2c),而原生 gRPC 依赖 HTTP/2 流式传输和二进制帧。前端直接 import @grpc/grpc-js 或调用 new grpc.Client() 会报错:Browser does not support HTTP/2 或直接连接失败。
必须走 gRPC-Web 协议——它把 gRPC 请求序列化为 base64 编码的 Protobuf,通过 HTTP/1.1 POST 发送,响应也走同样路径。但协议转换不能靠前端自己做,得靠中间层。
- 前端只能用官方
@grpc/grpc-web客户端(不是@grpc/grpc-js) - 服务端不能直接暴露 gRPC 端口给浏览器,必须加一层 gRPC-Web 代理(如
envoy、grpcwebproxy或 Nginx + Lua) -
grpcurl这类命令行工具默认走原生 gRPC,无法测试 gRPC-Web 接口,得用curl或专门的 Web 客户端
Envoy 是目前最稳的 gRPC-Web 代理选择吗?
是,尤其在生产环境。它原生支持 gRPC-Web → gRPC 的双向转换,且能复用已有的 TLS、路由、熔断等能力,不用额外维护 proxy 进程。
关键配置点藏在 envoy.yaml 的 http_filters 里,漏掉 grpc_web filter 就等于没开开关:
立即学习“前端免费学习笔记(深入)”;
http_filters: - name: envoy.filters.http.grpc_web - name: envoy.filters.http.router
- 必须开启
access_log_path: "/dev/stdout",否则 415 错误(Unsupported Media Type)很难定位是编码问题还是路径没配对 - 前端请求路径要和 Envoy 的
route配置严格一致,比如后端 gRPC service 是helloworld.Greeter,Envoy 必须把/helloworld.Greeter/SayHello转发到后端 gRPC 地址 - 不要用
grpcwebproxy做线上网关——它不支持 streaming,且长连接稳定性差,容易触发浏览器 pending 请求卡死
前端调用时 unary 和 stream 的行为差异有多大?
差别非常大:gRPC-Web 规范只正式支持 unary(一发一收),streaming(server-stream / client-stream / bidi)是实验性功能,需服务端、代理、客户端三方同时开启,且浏览器兼容性极差。
例如 Chrome 115+ 才开始支持 fetch + ReadableStream 解析 server-stream 响应;Safari 目前完全不支持。强行用会静默失败或卡在 Pending 状态。
- unary 调用:用
client.sayHello(request, metadata)即可,返回 Promise,和普通 REST 调用体验接近 - server-stream 调用:必须显式启用
withStreaming: true,且服务端返回的 content-type 必须是application/grpc-web-text(base64)或application/grpc-web+proto(binary),二者不可混用 - metadata 里传
encoding参数无效,实际编码由代理决定,前端只需确保Content-Typeheader 和请求体格式匹配
Protobuf 文件生成时 ts-proto 和 protoc-gen-grpc-web 怎么选?
如果用 TypeScript 写前端,优先选 ts-proto;如果项目还混着 JS 或需要强类型校验,再考虑 protoc-gen-grpc-web。
ts-proto 生成的是纯 TS 类型 + fetch 实现,轻量、可 tree-shake、不带运行时依赖;而 protoc-gen-grpc-web 生成的代码强耦合 @grpc/grpc-web,且默认带 jspb(Google 的 JS Protobuf 库),体积大、调试难。
-
ts-proto默认不生成 streaming 方法,要加--ts_proto_opt=useAbortSignal=true,oneof=unions才支持 cancel 和 union 类型 - 生成路径必须和
import路径一致,否则Uncaught Error: Cannot find module—— 尤其注意proto_root和out_dir的相对关系 - 别让
protoc直接读.proto文件里的import,要用-I指定所有依赖路径,否则生成的类型里会出现unknown字段
真正麻烦的从来不是“能不能通”,而是二进制 payload 在 HTTP/1.1 上被 chunk 分割后,代理有没有正确 reassemble,以及前端有没有按规范处理 base64 padding。这些细节不打日志根本看不见。










