把 gRPC 服务暴露成 REST,中间那层 grpc-gateway 到底做了什么、花多少?错误码怎么翻译、头怎么传、超时和流怎么办?这篇用真实生成的 gateway 代码跑了一遍。
POST /v1/echo → HTTP 200,{"id":"a","name":"alice","count":"123","payload":"aGVsbG8=","tags":["x","y"]}
GET /v1/items/abc?name=bob → HTTP 200,{"id":"abc","name":"bob","count":"1","payload":"","tags":[]}
GET /v1/items/abc?nope=1(未知查询参数)→ HTTP 200,{"id":"abc","name":"","count":"1","payload":"","tags":[]}
GET /v1/nothing → HTTP 404,{"code":5,"message":"Not Found","details":[]}
DELETE /v1/echo(路径存在但方法不对)→ HTTP 501,{"code":12,"message":"Method Not Allowed","details":[]}
POST 非法 JSON → HTTP 400,{"code":3,"message":"unexpected EOF","details":[]}从带 google.api.http 注解的 .proto 生成的反向代理:HTTP/JSON 请求进来,转成 gRPC 调用转发给后端,再把响应转回 JSON。
gRPC Canceled → HTTP 499
gRPC Unknown → HTTP 500
gRPC InvalidArgument → HTTP 400 {"code":3,"message":"failed with 3","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"BAD","domain":"demo.example","metadata":{}}]}
gRPC DeadlineExceeded → HTTP 504
gRPC NotFound → HTTP 404
gRPC AlreadyExists → HTTP 409
gRPC PermissionDenied → HTTP 403
gRPC ResourceExhausted → HTTP 429
gRPC FailedPrecondition → HTTP 400
gRPC Aborted → HTTP 409
gRPC OutOfRange → HTTP 400
gRPC Unimplemented → HTTP 501
gRPC Internal → HTTP 500
gRPC Unavailable → HTTP 503
gRPC DataLoss → HTTP 500
gRPC Unauthenticated → HTTP 401gRPC 的 16 种错误码映射到 HTTP 状态,是多对一的(400、500、409 各对应多个),响应体里保留原始的 code、message 和 details。
payload 0 字节:protobuf 26 字节,JSON 响应体 85 字节(3.27 倍);单次中位数 直接 gRPC 68 µs、经 gateway 的 REST 146 µs(2.1 倍) payload 100 字节:protobuf 128 字节,JSON 响应体 221 字节(1.73 倍);单次中位数 直接 gRPC 67 µs、经 gateway 的 REST 148 µs(2.2 倍) payload 102400 字节:protobuf 102430 字节,JSON 响应体 136621 字节(1.33 倍);单次中位数 直接 gRPC 246 µs、经 gateway 的 REST 1220 µs(5.0 倍) 16 个并发、小消息:直接 gRPC 59336 请求/秒,经 gateway 的 REST 25095 请求/秒(2.4 倍)
JSON 比 protobuf 大(gateway 默认还输出零值字段,bytes 用 base64),多一跳转发加上 JSON 编解码,单次耗时和吞吐都不如直连 gRPC。本机上 HTTP/1.1 的 REST 客户端与 gRPC 的差别包含协议的差别。
默认:服务端 metadata(去掉标准头)= {"id":"","name":"","count":"0","payload":"","tags":["authorization=Bearer t1","foo=f1","grpcgateway-authorization=Bearer t1","grpcgateway-user-agent=Go-http-client/1.1","x-forwarded-for=127.0.0.1","x-forwarded-host=127.0.0.1:9807"]}
WithIncomingHeaderMatcher 放行 X-Custom:{"id":"","name":"","count":"0","payload":"","tags":["authorization=Bearer t1","foo=f1","grpcgateway-authorization=Bearer t1","grpcgateway-user-agent=Go-http-client/1.1","x-custom=c1","x-forwarded-for=127.0.0.1","x-forwarded-host=127.0.0.1:9809"]}Authorization 和 Grpc-Metadata- 前缀的头会被转发,其它自定义头默认不转发。
HTTP 客户端超时(102.1 ms 后放弃,err 非空=true);后端 handler 的结果:"canceled"
Grpc-Timeout: 300m:HTTP 504,303.2 ms 后返回;{"code":4,"message":"context deadline exceeded","details":[]}HTTP 客户端断开会取消后端的调用;Grpc-Timeout 头可以设置后端的 deadline。
HTTP 200,响应体共 3 行:
{"result":{"id":"0","name":"","count":"0","payload":"","tags":[]}}
{"result":{"id":"1","name":"","count":"0","payload":"","tags":[]}}
{"result":{"id":"2","name":"","count":"0","payload":"","tags":[]}}服务端流变成逐行 JSON(每行包一层 result)。客户端流和双向流默认不支持。