浏览器为什么不能直接调 gRPC?grpc-web 到底改了什么?这篇用本机的 Chrome 和一个真实的 grpc-web Go 包装,把响应的每一个字节都打印出来。
成功的调用(msg=hi):HTTP 200、协议 h2;响应头可见:content-type,trailer;grpc-status=None、grpc-message=None;响应体 14 字节 00000000090a076563686f3a6869 被服务端拒绝的调用(msg=deny,服务端返回 Unauthenticated):HTTP 200、协议 h2;响应头可见:content-type,trailer;grpc-status=None、grpc-message=None;响应体 0 字节
gRPC 把状态放在 HTTP/2 的 trailers 里,而浏览器的 fetch/XHR 不让 JavaScript 读 trailers。调原生 gRPC 能读到数据,读不到成功还是失败。
fetch 的结果:TypeError: Failed to fetch;浏览器实际发出的请求方法:['POST', 'OPTIONS']
content-type 为 application/grpc 的跨域请求会触发预检,gRPC 服务端不会回应预检。需要中间层处理 CORS。
成功的调用:HTTP 200、content-type=application/grpc-web+proto、响应头 grpc-status=None;响应体 35 字节,共 2 帧
数据帧(标志位 0,长度 9):'0a076563686f3a6869'
trailers 帧(标志位 128,长度 16):'grpc-status: 0\r\n'
被拒绝的调用:HTTP 200、content-type=application/grpc-web+proto、响应头 grpc-status=16;响应体 0 字节,共 0 帧用 application/grpc-web+proto,响应体里除数据帧外,多一帧标志位为 0x80 的 trailers 帧,内容是 HTTP 头文本(如 grpc-status: 0)。JS 从响应体里就能读到状态。
3 ms:标志位 0 '0a026d30' 205 ms:标志位 0 '0a026d311001' 407 ms:标志位 0 '0a026d321002' 607 ms:标志位 128 'grpc-status: 0'
服务端流可以用 fetch 的 ReadableStream 边收边读。客户端流在浏览器里受限于请求体不能边发边写。
HTTP 200;响应帧:[(0, '1002'), (128, 'grpc-status: 0\r\n')]
grpc-web 不是原生 gRPC,需要一个能翻译的中间层(Envoy 的 grpc_web 过滤器、improbable-eng 的 Go 包装等)放在 gRPC 服务前面。