grpcurlで快適gRPC通信テスト

# 経緯

goで作っているサービスのgRPC通信の動作確認用に`grpcurl`というクライアントツールを使ってみました。

別クライアントの`grpc_cli`はMacローカルにbrewでインストールしてもうまくパスが通らず諦めました。

### 環境
```bash
$ sw_vers
ProductName:	macOS
ProductVersion:	11.5.1
BuildVersion:	20G80
$ go version
go version go1.16.6 darwin/arm64
$grpcurl --version
grpcurl 1.8.2
```

インストールはおもむろに `brew install grpcurl` で行いました。

#手順

### 1. サーバーを立ち上げる

```bash
$ cd $GOPATH/src/github.com/hoge-monorepo
$ go run cmd/xxxx/main.go
{"level":"INFO","ts":"2021-08-16T16:33:21.649229+09:00","caller":"auth/main.go:81","function":"main.runGRPCServer","message":"Start GRPC server at [::]:50051, TLS = false","hostname":"XXXs-M1-MacBook-Pro.local"}
```
*xxxxはサービス名

### 2. 利用可能なサービス一覧を確認する

```bash
$ grpcurl localhost:50051
Failed to dial target host "localhost:50051": tls: first record does not look like a TLS handshake
```
あれ？

```bash
$ grpcurl --help
...
-plaintext
    	Use plain-text HTTP/2 when connecting to server (no TLS).
...
```

TLS無しのアクセス時には`-plaintext`は必須。ということで

```bash
$ grpcurl -plaintext localhost:50051 list
grpc.reflection.v1alpha.ServerReflection
proto.AuthManagement

$ grpcurl -plaintext localhost:50051 describe
grpc.reflection.v1alpha.ServerReflection is a service:
service ServerReflection {
  rpc ServerReflectionInfo ( stream .grpc.reflection.v1alpha.ServerReflectionRequest ) returns ( stream .grpc.reflection.v1alpha.ServerReflectionResponse );
}
proto.AuthManagement is a service:
service AuthManagement {
  rpc Login ( .proto.LoginRequest ) returns ( .proto.LoginResponse ) {
    option (.google.api.http) = { post:"/auth/v1/login" body:"*"  };
  }
  rpc VerifyToken ( .proto.VerifyTokenRequest ) returns ( .proto.VerifyTokenResponse ) {
    option (.google.api.http) = { put:"/auth/v1/verify-token" body:"*"  };
  }
}
```
これで、このAPIが露出している全てのサービス`all exposed services`を出力できました。
（gRPCのmethodは全てPOSTなので、通常のcURLのように`-X POST`のような指定は不要です。）


### 3-1. 実際にAPIコールしてみる

```bash
$ grpcurl -plaintext localhost:50051 list proto.AuthManagement
proto.AuthManagement.Login
proto.AuthManagement.VerifyToken

$ grpcurl -plaintext -d '{"email": "hoge@example.com", "password": "1234"}' localhost:50051 proto.AuthManagement/Login
{
  "data": {
    "access_token": "eyJhbGciOi......",
    "refresh_token": "eyJhbGciOi......"
  }
}
```

### 3-2. リクエストパラメータを標準入力で入れたい場合

```bash
$ grpcurl -plaintext -d @ localhost:50051 proto.AuthManagement/Login
（ここで標準入力の待ち受けモードとなるので、おもむろにJSONをペースト。）
{
"email": "hoge@example.com",
"password": "1234"
}
（ここで「control + D」を押して終了。）
{D
  "data": {
    "access_token": "eyJhbGciOi......",
    "refresh_token": "eyJhbGciOi......"
  }
}
```

### 3-3. より詳細な情報を標準出力に表示したい場合

`-v`か`-vv`で出力が冗長（verbose）になります。

```bash
$ grpcurl -v -plaintext -d '{"email": "hoge@example.com", "password": "1234"}' localhost:50051 proto.AuthManagement/Login

Resolved method descriptor:
rpc Login ( .proto.LoginRequest ) returns ( .proto.LoginResponse ) {
  option (.google.api.http) = { post:"/auth/v1/login" body:"*"  };
}

Request metadata to send:
(empty)

Response headers received:
content-type: application/grpc

Response contents:
{
  "data": {
    "access_token": "eyJhbGciOi......",
    "refresh_token": "eyJhbGciOi......"
  }
}

Response trailers received:
(empty)
Sent 1 request and received 1 response
```

以上、簡単に説明してきました。
他にも特殊なAPIコールの仕方が出てきたら追記したいと思います。

# 以下、2021.10.28追記

### 4. import pathを指定する場合

protoファイルを指定してgRPCコールを試す際に、protoファイル中のimport文がうまく動かないことがあります。

例えば以下のようなファイルがあったとします。

```bash
$ tree -L 3 ./proto
proto
├── google
│   └── api
│       ├── annotations.proto
│       ├── http.proto
│       └── httpbody.proto
└── account
    ├── auth_management_service.proto
    └── auth_message.proto
3 directories, 5 files
```

```proto:proto/account/auth_management_service.proto
syntax = "proto3"

package proro;

import "google/api/annotations.proto"; <------ import先参照不可エラー
import "auth_message.proro"; <------ import先参照不可エラー

option go_package = ".;account";

service AuthManagement {
  rpc Login(LoginRequest) returns (LoginResponse) {
    option (google.api.http) = {
      post : "/v1/login"
      body : "*"
    }
  }
}
```

---

この状態で徐に`grpcurl`コマンドを叩くと、

```bash
grpcurl -plaintext \
 -d '{"email": "hoge@example.com", "password": "1234"}' \
 -proto account/auth_management_service.proto \
 127.0.0.1:8011 \
 proto.AuthManagement/Login

Failed to process proto source files.: could not parse given files: open account/auth_management_service.proto: no such file or directory
```

と怒られてしまいます。


---
ここで`grpcurl`コマンドに`-import-path ./proto`オプションをつけると、

```bash
grpcurl -plaintext \
 -d '{"email": "hoge@example.com", "password": "1234"}' \
 -import-path ./proto \
 -proto account/auth_management_service.proto \
 127.0.0.1:8011 \
 proto.AuthManagement/Login

Failed to process proto source files.: could not parse given files: account/auth_management_service.proto:6:8: open proto/auth_management.proto: no such file or directory
```

となります。そのため、account/フォルダの場所を明示的に示してあげる必要があります。

---
`-import-path ./proto/account`を追加します。

```bash
grpcurl -plaintext \
 -d '{"email": "hoge@example.com", "password": "1234"}' \
 -import-path ./proto \
 -import-path ./proto/account \
 -proto account/auth_management_service.proto \
 127.0.0.1:8011 \
 proto.AuthManagement/Login

Failed to process proto source files.: could not parse given files: account/auth_message.proto:6:8: open proto/validate/validate.proto: no such file or directory
```

この`validate/validate.proto`というのはバリデーションのためのprotocプラグインで、このプロジェクト配下に存在するものではなく、ご自身のGOPATH配下のパスを指定する必要があります。

```bash
$ go get -d github.com/envoyproxy/protoc-gen-validate
```
これでご自身のGOPATH配下に`protoc-gen-validate`が無事インストールされます。

---

`-import-path ${GOPATH}/src/github.com/envoyproxy/protoc-gen-validate`を追加します。


```bash
grpcurl -plaintext \
 -d '{"email": "hoge@example.com", "password": "1234"}' \
 -import-path ./proto \
 -import-path ./proto/account \
 -import-path ${GOPATH}/src/github.com/envoyproxy/protoc-gen-validate \
 -proto account/auth_management_service.proto \
 127.0.0.1:8011 \
 proto.AuthManagement/Login

{
  "data": {
    "access_token": "eyJhbGciOi......",
    "refresh_token": "eyJhbGciOi......"
  }
}
```

これで無事、import文の参照切れがなくなりAPIコールが正しく通るようになりました。