🔍
📢

JSON 协议 payload 的 gRPC 用法

介绍

大家经常说 gRPC 是基于 Google Protocol Buffers payload 格式的,然而这不完全正确。gRPC payload 的默认格式是 Protobuf,但是 gRPC-Go 的实现中也对外暴露了 Codec interface ,它支持任意的 payload 编码。我们可以使用任何一种格式,包括你自己定义的二进制格式、flatbuffers、或者使用我们今天要讨论的 JSON ,作为请求和响应。

服务端准备

我已经基于 JSON payload 实现 了 grpc/encoding.Codec,创建了一个示例库。服务端的准备工作仅仅像引入一个包那样简单;

import _ "github.com/johanbrandhorst/grpc-json-example/codec"

这行代码注册了一个基于 json 内容的子类型 JSON Codec,我们在后面会看到这对于方便记忆很重要。

Request 示例

gRPC 客户端

使用 gRPC 客户端,你只需要使用合适的内容子类型作为 grpc.DialOption 来初始化:

import "github.com/johanbrandhorst/grpc-json-example/codec"
func main() {
    conn := grpc.Dial("localhost:1000",
        grpc.WithDefaultCallOptions(grpc.CallContentSubtype(codec.JSON{}.Name())),
    )
}

示例库代码包含有完整示例的客户端

cURL

更有趣的是,现在我们可以用 cURL 写出请求(和读取响应)!请求示例:

$ Echo -en '\x00\x00\x00\x00\x17{"id":1,"role":"ADMIN"}' | curl -ss -k --http2 \
        -H "Content-Type: application/grpc+json" \
        -H "TE:trailers" \
        --data-binary @- \
        https://localhost:10000/example.UserService/AddUser | od -bc
0000000 000 000 000 000 002 173 175
         \0  \0  \0  \0 002   {   }
0000007
$ Echo -en '\x00\x00\x00\x00\x17{"id":2,"role":"GUEST"}' | curl -ss -k --http2 \
        -H "Content-Type: application/grpc+json" \
        -H "TE:trailers" \
        --data-binary @- \
        https://localhost:10000/example.UserService/AddUser | od -bc
0000000 000 000 000 000 002 173 175
         \0  \0  \0  \0 002   {   }
0000007
$ Echo -en '\x00\x00\x00\x00\x02{}' | curl -k --http2 \
        -H "Content-Type: application/grpc+json" \
        -H "TE:trailers" \
        --data-binary @- \
        --output - \
        https://localhost:10000/example.UserService/ListUsers
F{"id":1,"role":"ADMIN","create_date":"2018-07-21T20:18:21.961080119Z"}F{"id":2,"role":"GUEST","create_date":"2018-07-21T20:18:29.225624852Z"}

解释

使用 cURL 发送请求需要手动把 gRPC HTTP2 message payload header 加到 payload:

'\x00\x00\x00\x00\x17{"id":1,"role":"ADMIN"}'
#<-->----------------------------------------- Compression boolean (1 byte)
#    <-------------->------------------------- Payload size (4 bytes)
#                    <--------------------->-- JSON payload

请求头必须包含 TE 和正确的 Content-Type

-H "Content-Type: application/grpc+json" -H "TE:trailers"

在 Content-Type 头中 application/grpc+ 后的字符串需要与服务端注册的 codec 的 Name() 相吻合。这就是内容子类.

endpoint 需要与 proto 包的名字、服务和方法三者的名字都匹配:

https://localhost:10000/example.UserService/AddUser

响应头与请求头一致:

'\0  \0  \0  \0 002   {   }'
#<-->------------------------ Compression boolean (1 byte)
#    <------------>---------- Payload size (4 bytes)
#                     <--->-- JSON payload

总结

我们已经展示了我们可以轻易地在 gRPC 中使用 JSON payload,甚至可以用 JSON payload 直接发送 cURL 请求到我们的 gRPC 服务,没有代理,没有 grpc 网关,除了引入一个必要的包也没有其他的准备工作。