サーバを作る以上、クライアントがないと開発が面倒です。 しかし、gRPC はバイナリプロトコルである HTTP/2 上で Protocol Buffer をシリアライズに使います。 このため、テキストプロトコルである HTTP/1.x のようにクライアントとして curl を使うといったことができません。 この問題を解決するためにどうすれば良いんやという話です。
grpcurl
この問題を解決する方法はいくつかあるのですが、公式で紹介されているのは grpc_cli
です。
ただし、grpc_cli
は自分でビルドする必要があるのと、サーバが後述する reflection に対応していないといけません。
そこで、今回は grpcurl を使うことにしました。
grpcurl は curl
のようにしてコマンドラインから gRPC を呼び出せるツールです。
百聞は一見にしかずで、公式からの引用ですが、呼び出しが以下のようになると言うと curl に慣れた方は分かりやすいでしょうか。
$ grpcurl -d '{"id": 1234, "tags": ["foo","bar"]}' grpc.server.com:443 my.custom.server.Service/Method
試すために
以下にベーシックな gRPC サーバを置きました。
- SayHello: Unary RPC
- SayHelloToMany: Bidirectional streaming RPC
として実装しています。
gRPC サーバのメタ情報
gRPC サーバが公開しているメソッドについては、もちろん .proto
ファイルに記載があるので、それを読み込ませれば分かります。
今回は -import-path
で .proto
ファイルが配置されているディレクトリを指定し、ファイル名を -proto
で渡しています。
$ grpcurl -plaintext -import-path . -proto helloworld.proto localhost:8000 list helloworld.Greeter
.proto
がなくても、サーバがリフレクションに対応していればこの情報は簡単に得ることができます。
以下がその出力ですが、reflection 用のサービスも一緒に公開されていることが分かります。
$ grpcurl -plaintext localhost:8000 list
grpc.reflection.v1alpha.ServerReflection
helloworld.Greeter
サービス名を引数にして list
を呼び出すことで、当該サービスで公開している RPC の一覧も得ることができます。
$ grpcurl -plaintext localhost:8000 list helloworld.Greeter
SayHello
SayHelloToMany
さらに詳しい情報は、describe
で得られます。
$ grpcurl -plaintext localhost:8000 describe helloworld.Greeter helloworld.Greeter is a service: { "name": "Greeter", "method": [ { "name": "SayHello", "inputType": ".helloworld.HelloRequest", "outputType": ".helloworld.HelloReply", "options": { } }, { "name": "SayHelloToMany", "inputType": ".helloworld.HelloRequest", "outputType": ".helloworld.HelloReply", "options": { }, "clientStreaming": true, "serverStreaming": true } ], "options": { } }
上記のとおり、SayHello
と SayHelloToMany
という 2 つの RPC が公開されていることが分かります。
RPC 呼出
Unary RPC 呼出
Unary RPC については、ほとんど curl と同じ形で呼び出すことができます。
$ grpcurl -plaintext -d '{ "name": "kiririmode" }' -rpc-header 'postscript: you dislike onion' localhost:8000 helloworld.Greeter/SayHello { "message": "Hello kiririmode" }
-d
オプションでは JSON 形式のボディを渡すことができ、grpcurl が Protocol Buffer に変換してリクエストを送信してくれます。
-rpc-header
はリクエストに詰める metaadata で、key-value を取ることができます。curl の -H
オプションと同じように思えば良いでしょう。
その後、ホスト名とポート、RPC 名を引数に実行することで、gRPC サーバにリクエストが送信され、レスポンスが返却されます。レスポンスは JSON 形式に変換されて、標準出力に出力されます。 実はレスポンスのヘッダにも情報を設定しているのですが、この情報は出力されませんでした。ここがちょっと残念です。
- 2019/06/09 追記
-v
フラグでレスポンスヘッダ (Header/Trailer) の情報も出力されます。see: gRPCにおけるmetadata、そしてそれを node.js client から取得する - 理系学生日記
Bidirectional streaming RPC 呼出
じゃぁ、Bidirectional streaming RPC はどのように呼び出せば良いのか。 これはちょっと悩んだんですが、以下のような形式で呼び出すことが可能でした。
grpcurl -plaintext -import-path . -proto helloworld.proto \ -d '{ "name": "kiririmode" }{ "name": "hyuichi" } {"name": "kiri"}'\ -rpc-header 'postscript: you dislike onion' \ localhost:8000 helloworld.Greeter/SayHelloToMany { "message": "Hello kiririmode" } { "message": "Hello hyuichi" } { "message": "Hello kiri" }
要するに、複数個の独立した JSON を与えてやれば良いです。そうすると、個々の JSON に対する応答が Streaming で返却されていることが分かります。
エラー表示
gRPC のエラーについて、どう表示されるのかなと思って試してみたのが以下です。 Code と Message が、それぞれアプリで設定したとおりに出力されることが分かります。
$ grpcurl -plaintext -d '{ "name": "error" }' localhost:8000 helloworld.Greeter/SayHello ERROR: Code: Internal Message: Internal Error
ということで
ずいぶんと使いやすいツールでした。 他にも polyglot など色々ツールはあったのですが、go で統一しようとすると grpcurl はかなりお勧めなかんじがします。