gRPCはRPCのフレームワークです。プロトコルはHTTP/2上に定義されています。普段gRPCを使っているもののプロトコルの仕様を見たことがなかったので見てみました。
動かしたサンプルはHello Worldです。 このサンプルでgRPCサーバは"Greeting: Hello <クライアントが送った文字列>"のようにレスポンスをします。 クライアントからは"world"という文字列を送っているので実行すると以下のようになります。
クライアント
$ go run greeter_client/main.go 2019/12/13 20:49:26 Greeting: Hello world
サーバ
$ go run greeter_server/main.go 2019/12/13 20:49:26 Received: world
これをWiresharkでキャプチャしてgRPCリクエスト部分だけ抜き出すと以下のようになっています。
HTTP/1.1ではHTTPリクエストとHTTPレスポンスがメッセージの最小の単位でした。HTTP/2ではHTTPフレームが最小の単位となっています。今回では
HEADERSフレーム + DATAフレーム
が1つのgRPCリクエストとなっています。
gRPCの定義は↓となっています。
Request → Request-Headers *Length-Prefixed-Message EOS
ABNF syntaxで記述されています。gRPCリクエストはRequest-Headers
と0個以上のLength-Prefixed-Message
とEOS
フラグが付いたLength-Prefixed-Messageから構成されます。
Request-Headers
がHTTP/2のHEADERSフレーム
で、Length-Prefixed-Message
がDATAフレーム
に対応します。EOS
はEOSフラグが付いたDATAフレームです。
このDATAフレームにgRPCリクエストのメッセージが入っているようです。今回のサンプルでは下記のようにメッセージが定義されています。
// The request message containing the user's name. message HelloRequest { string name = 1; }
HTTP/2 DATA Frame
DATAフレームを見ると↓のようになっています。(HEADERSフレームの説明は省略します)
HTTP/2のフレームは以下のように定義されています。(rfc7540より引用)
16進数で表されたDATAフレームを順に見ていきます。最初の3バイトはペイロードの長さを表しています。
00 00 0c
12バイト(0x0c)がフレームペイロードの長さで、Length-Prefixed-Message
が12バイトあるということです。次の1バイトはHTTP/2のフレームのタイプで、00
はDATAフレームを表しています。
00
次の1バイトはフラグで、今回は↓のようにEND_STREAM (0x1)
フラグが付いています。
01
このフラグは名前の通りHTTP/2のストリームの状態をhalf closed
にさせます。続いて、R
は常に0x0
である予約された1ビットです。その次のストリームIDが31ビットで合わせた4バイトは以下のようになっています。
00 00 00 01
ストリームIDは1です。
gRPC Message in DATA Frames
続いてフレームペイロード部分です。Wiresharkの表示がDATAフレームのあとにgRPC Messageが送られているようにも見えますが、DATAフレームの中にgRPC Messageがあります。
このDATAフレームはgRPCリクエストの定義のLength-Prefixed-Message
の部分ですが、さらに以下のように定義されています。
Length-Prefixed-Message → Compressed-Flag Message-Length Message
このLength-Prefixed-Message
は12バイトでした。最初の1バイトはCompressed-Flag
です。
00
これが1だとHEADERSフレーム中にあるMessage-Encoding header
で指定された形式でMessage
が圧縮されています。今回は圧縮されていないようです。次の4バイトはMessage
の長さです。
00 00 00 07
長さは7バイトです。そして最後が7バイトのMessageです。
0a 05 77 6f 72 6c 64
これはProtocol Buffersで、今回は既に掲載しましたが、
// The request message containing the user's name. message HelloRequest { string name = 1; }
というgRPCリクエストメッセージの定義で、このnameに"world"という文字列入れて送っています。Protocol BuffersではKey-Value形式にエンコーディングしています。この場合のKeyはstring name = 1
の1
です。nameという情報はMessageには含まれていません。(なのでプログラムで使う時はProtocol Buffersの定義ファイルをサーバとクライアントで共有することになります。) Valueはworld
という文字列です。エンコーディングされたMessageを説明するために2進数にします。
00001010 00000101 01110111 01101111 01110010 01101100 01100100
まずKeyの部分ですが、最初の1バイトである00001010
です。このmsb(最上位ビット)が1
だと次の1バイトもKeyを表しますが、今回はmsbは0
なので、最初の1バイトがKeyの部分となります。Keyの下位3ビットはValueのタイプを表していて今回は010
です。これはProtocol BuffersではLength-delimited
という文字列などで使われるタイプです。msbと下位3ビットを除いた0001
がKeyで1
です。
Length-delimited
のValue部分はValueの長さとValueから構成されます。2バイト目の00000101
が長さです。msbが次の1バイトにも続くかどうかのフラグになっているのは同様です。今回も0
なので0000101
が長さを表しています。長さは5です。"world"で5バイトですね。stringはUTF8として定義
されています。
01110111 01101111 01110010 01101100 01100100 (77 6f 72 6c 64)
UTF8の英数字はASCIIなので、確認するとworldとなっています。
gRPCレスポンス
レスポンスの定義は次のようになっています。
Response → (Response-Headers *Length-Prefixed-Message Trailers) / Trailers-Only
キャプチャしたgRPCレスポンスを見ると次のようになっています。
↑がResponse-Headers *Length-Prefixed-Message
で↓がTrailers
です。
Response-Headers
がHTTP/2のHEADERSフレーム
で、Length-Prefixed-Message
がDATAフレーム
に対応します。そして、Trailers
はEND_STREAMフラグが付いたHEADERSフレームです。DATAフレームにはリクエストの時と同様にgRPCレスポンスメッセージがあります。詳細に追うのは省略します。
Trailers
でストリームがclosedになります。(rfc7540より引用)
試しにserverからgRPCレスポンスでメッセージを返さずにエラーを返すと、Trailers-Only
になりました。
// return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil return nil, status.Error(codes.Internal, "something wrong")
最後に
Server streaming RPCなども追ってみたいです。またいずれ書きます。
HTTP/2の上のレイヤーとして定義されているので、HTTP-Statusを"200"に固定していたり、HTTPとしては意味をなしていない部分もあるようです。メリットの方が大きいということでしょうか。ちょっとググったらgRPC Design and Implementationを見つけました。
Leverage existing network protocols and infrastructure
や
Functional in production data center, but not on Internet (firewalls, etc)
Programmer responsible for all network management and data transfer
と書いています。Network managementはgRPCサーバがうまいことやってくれればいいかなとは思います。GoogleではSaaS事業者が提供しているAPIがgRPCみたいな感じにgRPCが使われている感じでしょうか。gRPCについて議論されているドキュメントや書籍あれば教えてください。
アウトプットの習慣が今までなかったり効率が悪くて記事を書くのにすごい時間かかる...。