gRPC-Webのメモ

ただの使ってみた記事です。

ScalaでAkka gRPCを利用してgRPCサーバを動かし、Envoy Proxyを介してgRPC-Webを触れるようにしたので、gRPC-Webのクライアントを構築してみた。

gRPC-Web クライアント

grpc.io この辺りのチュートリアルを見ながらなぞっただけなのでクライアント側は特に変わったことはしていない。 2個前くらいの記事に置いといたToDoサービス用のprotoファイルを使ってコードを生成した。

とりあえずpackage.jsonに以下の記述を追加してnpm経由で叩けるようにはしたが

"scripts": {
  "gen-todo": "protoc -I /path/to/protobuf --js_out=import_style=commonjs:service --grpc-web_out=import_style=commonjs,mode=grpcwebtext:service todo.proto"
}

いくつか設定値があるようなので、requireを使えるようにcommonjsにしたり、お試し用途なのでbinaryではなくplain textを扱う mode=grpcwebtext を指定した。

生成されたファイルからの依存があるので、grpc-webgoogle-protobuf dependenciesに追加する必要がある。

grpc-web - npm

google-protobuf - npm

あとは生成したファイルを素直に使い、Clientを初期化してRequestを渡してrpcメソッドを叩けば動く。 Repositoryとかインフラ層のレイヤーからここに依存させてメソッドを叩くだけで良いので、やれHTTP通信ライブラリ何にしようとか悩まなくて良くもなるのでかなり楽かもしれない。

ただ、ドキュメントで以下の記述になっているようにコールバックを渡す形式なのが困る。

echoService.echo(request, {}, function(err, response) {
  // ...
});

ので、RepositoryとかでPromise化してあげる必要がありそう。 というか外部との通信レイヤーとのインターフェースはすでにPromiseなはずなので、インターフェースを保ってあげるだけで良いんだな。

という形でクライアントサイドは非常に楽だったのだが、CORSの設定周りでドハマリして時間がかかった。

CORS

結局はEnvoy Proxyの設定の話でしか無いのだけど。

  • クライアントサイドからEnvoyにリクエストしたらCORSエラーが出た
  • EnvoyにCORSを許可する設定を入れたがうまく動かなかった
  • Akka gRPCのgRPC-Web対応のハンドラを試してみたがうまく動かなかった
  • EnvoyのCORS設定をし直して動いた

という流れで解決したが、時間がかかった。

最終的にEnvoyのCORS周りの設定値は以下のようになった。

          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains:
              - "*"
              cors:
                allow_origin_string_match:
                  - prefix: "*"
                allow_methods: "OPTIONS, GET, PUT, DELETE, POST"
                allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web
                expose_headers: grpc-status,grpc-message
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: todo_service
          http_filters:
          - name: envoy.filters.http.cors
          - name: envoy.filters.http.grpc_web
          - name: envoy.filters.http.router

もともと match の箇所にgrpcの設定値を入れていたのだが、あってはいけなかったらしい。 その設定値があるとOPTIONSメソッドでのリクエストがgrpcリクエストではないため正常にEnvoyでハンドリングされず、404だったり415だったりのエラーになってしまってgrpcリクエストが失敗してしまうという原因だった。

時間がかかる原因にもなったのだが、EvansのgRPC-Webオプション(--web)をつけるとInputting Canceledなど原因不明なエラーに遭遇し、これがAkka gRPCのエラーなのかEnvoyのものなのかなど調べる必要がでてしまったこともある。 結局EvansのwebオプションをつけるとgRPC-Webのドキュメントからリンクされてるオフィシャルのプロジェクトですら動かなかったので、Evans側の問題なのだろうということにした… 追えればEvansのコード追ってみたい。

あと、gRPC-Web自体がどう動くものなのか分かっていなかったので少し調べたが、以下の記事が参考になった。

https://blog.envoyproxy.io/envoy-and-grpc-web-a-fresh-new-alternative-to-rest-6504ce7eb880

ブラウザからgRPCサーバに対してのgRPCリクエストは送れないので、間に中継役が必要になり、それがEnvoyだということ。

ブラウザからEnvoyに対してはHTTPリクエストを送信し、Envoyから裏側のgRPCサーバに対してHTTP/2の通信をしてくれるということ、などが解説されていて勉強になった。

gRPCサーバ、クライアントの構築が完了して、どちらもこれ以降の実装イメージが湧いたのでOKとする。