理系学生日記

おまえはいつまで学生気分なのか

gRPCをcurlのようにコンソールから呼び出せるgrpcurl

サーバを作る以上、クライアントがないと開発が面倒です。 しかし、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 サーバを置きました。

  1. SayHello: Unary RPC
  2. 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": {

  }
}

上記のとおり、SayHelloSayHelloToMany という 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 形式に変換されて、標準出力に出力されます。 実はレスポンスのヘッダにも情報を設定しているのですが、この情報は出力されませんでした。ここがちょっと残念です。

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 はかなりお勧めなかんじがします。