きみはねこみたいなにゃんにゃんなまほう

ねこもスクリプトをかくなり

gRPC で go-proto-validators を .proto に import したら grpc_cli がうまく使えない

(=˘ ꒳ ˘=) 最近 gRPC を始めたけどパス解決周りが Protocol Buffer 層も相まって複雑で混乱する...

hello.proto に簡単なバリデーションを組み込んで Go で吐き出して grpc_cli で動作確認しようとしたらハマって、そしてそのまま解決していない件になります。バリデーション処理ではなく主に Protocol Buffer の import 周りの試行錯誤のお話になります。

素朴に go-proto-validators を import したところ...

今回対象にする hello.proto は以下のようなものです。 go-proto-validators/validator.protoimport しています。

syntax = "proto3";
package hello;

import "github.com/mwitkow/go-proto-validators/validator.proto";

service GreetingService {
  rpc Hello(GreetingRequest) returns (GreetingResponse);
}

message GreetingRequest {
  string name = 1 [(validator.field) = {string_not_empty: true}];
}

message GreetingResponse {
  string message = 1 [(validator.field) = {string_not_empty: true}];
}

これを protoc して Go ライブラリを吐き出してサーバを実装して grpc_cli で叩いたところ、症状としては以下のようになりました。

  • grpc_cli ls は動く
  • grpc_cli ls -l は動かない
  • grpc_cli call ... も動かない
$ grpc_cli ls localhost:10000
grpc.reflection.v1alpha.ServerReflection
hello.GreetingService

$ grpc_cli ls -l localhost:10000
[libprotobuf ERROR google/protobuf/descriptor.cc:3592] Invalid proto descriptor for file "api/hello.proto":
[libprotobuf ERROR google/protobuf/descriptor.cc:3595]   api/hello.proto: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors.
filename: grpc_reflection_v1alpha/reflection.proto
package: grpc.reflection.v1alpha;
service ServerReflection {
  rpc ServerReflectionInfo(stream grpc.reflection.v1alpha.ServerReflectionRequest) returns (stream grpc.reflection.v1alpha.ServerReflectionResponse) {}
}

$ grpc_cli call localhost:10000 Hello "name: 'test'"
[libprotobuf ERROR google/protobuf/descriptor.cc:3592] Invalid proto descriptor for file "api/hello.proto":
[libprotobuf ERROR google/protobuf/descriptor.cc:3595]   api/hello.proto: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors.
Method name not found

どれも github.com/mwitkow/go-proto-validators/validator.proto が見つからないと言われていますね。

gRPC や Protocol Buffer 周りのパス解決をよく理解しておらず、さらにリフレクション経由というローカルとはいえネットワークを噛ませたパス解決がどうなるかも相まって二重三重で混乱します。

とりあえずリフレクションを無効化して試行錯誤する

問題を簡略化したいので、とりあえずリフレクションはコメントアウトして grcp_cli --protofile を直接指定するやり方で実行してみます。

https://github.com/grpc/grpc/blob/master/doc/command_line_tool.md#call-a-remote-method の「User local proto file」のやり方です。

grpc_cli にも --proto_path を指定できるみたいですが protoc のように複数指定してもうまく行かず、結局 .proto のパスを同一起点から解決できればいいんだろうということで、プロジェクトルートから依存パッケージを辿れるような symlink を張ったら動くことには動きました。

$ echo $GOPATH
/Users/asa-taka

$ pwd
/Users/asa-taka/src/github.com/asa-taka/hello-validated-grpc

$ tree
.
├── Makefile
├── api
│   ├── hello.pb.go
│   ├── hello.proto
│   └── hello.validator.pb.go
├── github.com -> /Users/asa-taka/src/github.com/
├── google -> /Users/asa-taka/src/github.com/google/protobuf/src/google
└── server.go

$ grpc call localhost:10000 Hello "name: 'test'" --protofiles=api/hello.proto connecting to localhost:10000
message: "Hello, test."

Rpc succeeded with OK status

流石に不便なのでもっといい方法が用意されているか、何かしら自分が間違えているのを期待しています。

一応どの symlink を張ったか解説しておくと

ちなみに grpc_cli ls は動作しませんでした。リフレクション専用のコマンドみたいですね。

$ grpc ls -l localhost:10000 --protofiles=api/hello.proto
Received an error when querying services endpoint.

この状態からリフレクションのコメントアウトを外してみます。初めの状態と特に症状は変わりませんでした。依然として github.com/mwitkow/go-proto-validators/validator.proto が見つからないと言われています。

$ grpc ls localhost:10000
grpc.reflection.v1alpha.ServerReflection
hello.GreetingService

$ grpc ls -l localhost:10000
[libprotobuf ERROR google/protobuf/descriptor.cc:3592] Invalid proto descriptor for file "api/hello.proto":
[libprotobuf ERROR google/protobuf/descriptor.cc:3595]   api/hello.proto: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors.
filename: grpc_reflection_v1alpha/reflection.proto
package: grpc.reflection.v1alpha;
service ServerReflection {
  rpc ServerReflectionInfo(stream grpc.reflection.v1alpha.ServerReflectionRequest) returns (stream grpc.reflection.v1alpha.ServerReflectionResponse) {}
}

$ grpc call localhost:10000 Hello "name: 'test'"
[libprotobuf ERROR google/protobuf/descriptor.cc:3592] Invalid proto descriptor for file "api/hello.proto":
[libprotobuf ERROR google/protobuf/descriptor.cc:3595]   api/hello.proto: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors.
Method name not found

苦し紛れに --proto_path=. を指定してみても改善せず。

$ grpc ls -l localhost:10000 --proto_path=.
[libprotobuf ERROR google/protobuf/descriptor.cc:3592] Invalid proto descriptor for file "api/hello.proto":
[libprotobuf ERROR google/protobuf/descriptor.cc:3595]   api/hello.proto: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors.
filename: grpc_reflection_v1alpha/reflection.proto
package: grpc.reflection.v1alpha;
service ServerReflection {
  rpc ServerReflectionInfo(stream grpc.reflection.v1alpha.ServerReflectionRequest) returns (stream grpc.reflection.v1alpha.ServerReflectionResponse) {}
}

$ grpc call localhost:10000 Hello "name: 'test'" --proto_path=.
[libprotobuf ERROR google/protobuf/descriptor.cc:3592] Invalid proto descriptor for file "api/hello.proto":
[libprotobuf ERROR google/protobuf/descriptor.cc:3595]   api/hello.proto: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors.
Method name not found

リフレクションサービスは動いているけれど、import が解決できず GreetingService は動かないという...

リフレクション経由で .proto の import を解決する方法ってないんでしょうか。今のところ symlink を張るくらいの解決策しか見つかっておらず、このままだとちょっと不便なんですよね...何かあるはず、というかみんなどうやってるのか...

Issue を漁ってみる...

コマンドのヘルプもドキュメントもあまり充実してはいないので、諦めて Issue を辿っていきます。

ざっと見たところ google.protobuf.Timestamp というよく使われる type/message を使った時も面倒なことになっているらしいですが、詳しいことは読み取る気力がわきませんでした。こちらの方が事象としてはよりシンプルな気もするので、こちら側から攻めてみてもいいかもしれませんね。どうせ日付型も使うことになるでしょうし。

...で、別記事で日付型の import を試してみたところ普通に動いてしまったというわけで、本件に関しては継続して探っていきたいと思います...

それにしても Issue を辿っていると protobuf と grpc と grpc-go をたらい回しになって、なんか、その、疲れます...