3 minutes
gRPCのCodecをmsgpackに変更する
お世話になっております。
しゃまとんです😊
gPRCのデフォルトシリアライザにはProtocol Buffersが使われています。
以前から、JSONに置き換えることはこちらの記事で知っていたのですが、JSONに置き換えられるのならMessagePackにも出来るのかな?と思ったのでやってみました。
ちなみに Unity/C# では、MagicOnion というgRPC上にMessagePackがのっているフレームワークが公開されておりますね。
手元で試す場合まずは、protocなどのコマンドを用意する必要がありますが、最新のバージョンを入れるようにしましょう!出力結果や実行オプションが違ってたりしました。
しゃらくせぇ!って方は今回試したものをリポジトリにしているのでよしなにしてください。
CodecはJSONに習って encoding.Codec
を満たすようにしておけば良いです。(試していたときは難しいのでは…?と思っていたけど、そんなことはなかった。さすがgoogle)
リポジトリにはgRPCのexampleにある HelloRequest/HelloResponse
を使っていますが、
ここにはこれらのデータがinterfaceで入ってくるので、ただシリアライズすればよいのですね。
const MsgpackCodecName = "msgpack"
type msgpackCodec struct {
}
func (c *msgpackCodec) Marshal(v interface{}) ([]byte, error) {
// protoからgenerateされていればproto.Messageを満たすはず
msg, ok := v.(proto.Message)
if !ok {
return nil, fmt.Errorf("not a proto message but %T: %v", v, v)
}
return msgpack.Marshal(msg)
}
func (c *msgpackCodec) Unmarshal(data []byte, v interface{}) error {
_, ok := v.(proto.Message)
if !ok {
return fmt.Errorf("not a proto message but %T: %v", v, v)
}
return msgpack.Unmarshal(data, v)
}
func (c *msgpackCodec) Name() string {
return MsgpackCodecName
}
あとはCodecをサーバ起動時に登録し、クライアントでは使うように設定すればよいです。
// server
func main() {
encoding.RegisterCodec()
}
// client
opts := []grpc.DialOption{
grpc.WithDefaultCallOptions(grpc.CallContentSubtype(encoding.MsgpackCodecName)),
}
ここで、HelloRequestのコードを見てみましょう。JSONはタグが使われていますね。 msgpackはタグがありません。つけなくても動くのは動くんですが、JSONと同じようについてないのでなんとかしてつけたくなりました。(データとして使うメンバ以外はprivateになっているため) どうやってやればいいのかというとprotobuf-goのこの辺をイジる必要があるんですね。 うまいこと注入できる感じだと良かったんだけどなーという印象。
https://github.com/grpc/grpc-go/blob/master/examples/helloworld/helloworld/helloworld.pb.go#L48
type HelloRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
仕方がなかったので、protobuf-goをforkして、tagを設定している箇所でmsgpackも同じように設定してあげるようにしました。
これを加えた上で protoc を実行すると…タグがつくようになりました 🙆
一応、この状態で実行してみると… replyが返ってきます。
// go run ./cmd/client
2022/01/09 22:24:27 send bytes: 81 a4 6e 61 6d 65 a5 77 6f 72 6c 64
2022/01/09 22:24:27 recv bytes: 81 a7 6d 65 73 73 61 67 65 ac 48 65 6c 6c 6f 20 77 6f 72 6c 64 2e
2022/01/09 22:24:27 Greeting: Hello world.
// go run ./cmd/server
2022/01/09 22:24:27 recv bytes: 81 a4 6e 61 6d 65 a5 77 6f 72 6c 64
2022/01/09 22:24:27 call from world
2022/01/09 22:24:27 send bytes: 81 a7 6d 65 73 73 61 67 65 ac 48 65 6c 6c 6f 20 77 6f 72 6c 64 2e
また、Marshal/Unmarshalしているときのバイナリを確認してみると 当たり前なんですがmsgpackのデータ構造になっていますね。(81はfixmap)
protobuf-goを管理するのはつらいので、やるならmsgpackをjsonタグでも動くようにする…みたいなのがよいのかも… と思いましたが、無理してやる必要はないと思いましたw
以上です〜👋