第 1 篇提到调用结果放在最后的 trailers 里。为什么非得这样?什么叫 trailers-only?message 里的中文怎么传?trailers 没来会怎样?这篇逐个抓包验证。
客户端:收到 2 条消息之后 Recv 返回 code=Internal message="第三条出错了"
S→C HEADERS 标志 END_HEADERS :status: 200; content-type: application/grpc
S→C DATA 标志 - 消息 5 字节
S→C DATA 标志 - 消息 5 字节
S→C HEADERS 标志 END_STREAM|END_HEADERS grpc-status: 13; grpc-message: %E7%AC%AC%E4%B8%89%E6%9D%A1%E5%87%BA%E9%94%99%E4%BA%86响应的 HTTP 状态在第一个 HEADERS 里就确定了(永远是 200),而流式 RPC 要到发完消息才知道成败,所以成败写在最后的 trailers(带 END_STREAM 的 HEADERS)里。
客户端:收到 0 条消息,code=PermissionDenied message="一条都没发就失败"
S→C HEADERS 标志 END_STREAM|END_HEADERS :status: 200; content-type: application/grpc; grpc-status: 7; grpc-message: %E4%B8%80%E6%9D%A1%E9%83%BD%E6%B2%A1%E5%8F%91%E5%B0%B1%E5%A4%B1%E8%B4%A5
对照:正常返回 3 条消息的流,服务端的帧是:
S→C HEADERS 标志 END_HEADERS :status: 200; content-type: application/grpc
S→C DATA 标志 - 消息 4 字节
S→C DATA 标志 - 消息 4 字节
S→C DATA 标志 - 消息 4 字节
S→C HEADERS 标志 END_STREAM|END_HEADERS grpc-status: 0; grpc-message: 失败得早、一条消息都没发时,服务端把 :status、content-type、grpc-status、grpc-message 合并成一个带 END_STREAM 的 HEADERS,没有 DATA。
线上 grpc-message: %E5%8F%82%E6%95%B0%E9%94%99%E8%AF%AF:id=100%25%0A%E7%AC%AC%E4%BA%8C%E8%A1%8C 客户端解码后 message="参数错误:id=100%\n第二行";与服务端原文相同:true
grpc-message 的值做百分号编码,所以任意字符都能放;客户端自动解码。线上会变长,中文尤其明显。
客户端 trailer x-served-by = ["node-7"] 客户端 trailer x-cost-ms = ["12"] 客户端 trailer x-blob-bin = ["\x00\x01\xfe\xff"] 线上 trailers 帧:grpc-status: 0; grpc-message: ; x-cost-ms: 12; x-blob-bin: AAH+/w; x-served-by: node-7
服务端可以在响应末尾带自己的 trailers(耗时、实例名等),二进制值用 -bin 结尾的键,线上是无填充的 base64。
客户端先收到 1 条消息,然后 code=Unavailable message="error reading from server: EOF"
连接中途断开:客户端收到 Unavailable(error reading from server: EOF),已收的消息不丢,但不知道服务端有没有处理完。
服务器回了合法的消息,但没有 grpc-status:resp=<nil> code=Internal message="server closed the stream without sending trailers"
HTTP/1.1 代理等不转发 trailers 的组件会让客户端报 Internal: server closed the stream without sending trailers。这是 gRPC 要求路径上都是支持 trailers 的 HTTP/2 的原因。