bigywater.tech
gRPC · 4

grpc-status 与 grpc-message 为什么放在 trailers 里

第 1 篇提到调用结果放在最后的 trailers 里。为什么非得这样?什么叫 trailers-only?message 里的中文怎么传?trailers 没来会怎样?这篇逐个抓包验证。

1 对 5
一条消息都没发就失败(trailers-only)与正常返回 3 条消息的流,响应的帧数
%E5%8F%82…
中文 grpc-message 在线上的百分号编码,客户端自动解码还原
Internal
服务器缺少 trailers 时客户端的错误:server closed the stream without sending 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)里。

trailers-only

实测 · 本机执行输出
  客户端:收到 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。

message 的编码

实测 · 本机执行输出
  线上 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 的值做百分号编码,所以任意字符都能放;客户端自动解码。线上会变长,中文尤其明显。

应用 trailers

实测 · 本机执行输出
  客户端 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。

trailers 没来

实测 · 本机执行输出
  客户端先收到 1 条消息,然后 code=Unavailable message="error reading from server: EOF"

连接中途断开:客户端收到 Unavailable(error reading from server: EOF),已收的消息不丢,但不知道服务端有没有处理完。

不支持 trailers 的中间层

实测 · 本机执行输出
  服务器回了合法的消息,但没有 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 的原因。

分步演示

trailers
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1 / grpc-go v1.84.0 / protobuf-go v1.36.12 / golang.org/x/net v0.57.0 / protoc 36.2(Homebrew);服务端和客户端都是本机起的 Go 进程(127.0.0.1,端口 9800 以上,不访问任何外部服务);需要看线上字节时用我自己写的 TCP 抓包代理加 golang.org/x/net/http2 的 Framer 解析 HTTP/2 帧;计时用 time.Now 或 perf_counter,数字每次运行会有小幅抖动。第五节用抓包代理的 CutAll 在服务端发了一条消息之后掐断所有 TCP 连接;第六节的“不发 trailers 的服务器”是 h2c 的 net/http 服务,只写了 HEADERS 和一条合法的 gRPC 消息;所有结果是确定的
    • 没有验证:Java、C++ 客户端在缺少 trailers 时是否报同样的错;HTTP/1.1 网关的具体行为;trailers 的大小限制;grpc-message 的长度限制(头列表大小上限);“x-blob-bin” 以外的二进制元数据在 HEADERS 里的表现(第 7 篇)
    京ICP备2026067502号-1京公网安备11010502063487号