gRPC-WEBについて調べたのでまとめる
TL;DR
- なんか色々複雑な話だった
gRPCとは
- gRPCのRPCは
Remote Procedure Call
だが、gの部分はGodとかGoogleとか諸説あるらしい🥺 - Protocol Buffers を使ってデータを変換して通信する(変換後にzip処理もできる)
- IDL(インターフェース定義言語)(.protoファイル)があるため、スキーマファーストで開発できる
- HTTP/2を使用して作られている(HTTP/3を使うバージョンも検討中っぽい?)
- HTTP Trailer(後述する)を多用している
- HTTP/2経由のバイナリ通信と同じでは?と思うかもしれないが、より高度な機能を提供しているらしい
話は逸れるが HTTP Trailer について
Transfer-Encoding: chunkedのときのみ利用可能で、Body送信後にしか分からないチェックサムなどをリクエストに付与したいときなどに使うもの。
gRPC は HTTP Trailer を多用するが、Web ブラウザーを含む 多くの HTTP 実装は、まだトレーラーをサポートしていない。
gRPC-WEB では、応答本文の末尾にトレーラーをエンコードして付与することでその問題を解決しているとのこと。
ちなみに、2024-09現在、Nginxではbodyにトレーラーを付ける実装が無いため、不完全なgRPC-WEB実装となっていて後述するconnect-rpcで使えなかったりする。
gRPC-WEBとは
ブラウザが対応しなくても、gRPCを使えるようにしたもの。
- HTTP1.1でも動作するように、変換処理を挟んで通信する(envoy, nginxなど)
- 変換処理込みのgRPCサーバがあれば、envoyなどは不要となる(Rustのtonic-webで確認)
- gRPCの
Unary RPCs
、Server-side Streaming RPCs
のみサポート - クライアントストリーミング RPC と双方向ストリーミング RPC は未対応(HTTP/2のみの機能なため)
- POSTリクエストで、Protocol Buffersのバイナリを送信するスタイル
- content-typeは、
application/grpc-web
orapplication/grpc-web+proto
curlでgRPC-WEBをコールする場合は下記のような感じとなる
## (--data-rawだとうまくいかないので、--data-binaryに標準入力からバイナリを渡している)
printf '\x00\x00\x00\x00\x05\n\x03aaa' \
| curl -v 'http://[::1]:50051/helloworld.Greeter/SayHello' \
-X POST \
-H 'content-type: application/grpc-web+proto' \
--data-binary '@-' \
--output -
結果は下記のような感じ
> POST /helloworld.Greeter/SayHello HTTP/1.1
> Host: [::1]:50051
> User-Agent: curl/8.7.1
> Accept: */*
> content-type: application/grpc-web+proto
> Content-Length: 10
>
* upload completely sent off: 10 bytes
< HTTP/1.1 200 OK
< content-type: application/grpc-web+proto
< access-control-allow-origin: *
< vary: origin
< vary: access-control-request-method
< vary: access-control-request-headers
< transfer-encoding: chunked
< date: Wed, 21 Aug 2024 04:41:04 GMT
HTTP Trailer
を使う場合、transfer-encoding: chunked
である必要があるらしい。
gRPC-Gatewayとは
gRPC-WEBと似ているが、変換部分がJSONとなっており、より簡単に通信できる。
だがしかし、下記の欠点がある。
- proto ファイルが変更された場合、gRPC-Gatewayサーバのコードも再生成する必要がある(リリースが複雑になる)
- protobufからjsonに変換しているのでオーバーヘッドがある(gRPC-WEBは、protobufをそのまま使っている)
- proto ファイルからフロントエンドのコードが生成されるわけではないので、管理が煩雑?(コード生成までやるライブラリがあれば別だがそこまで調べてない)
Connect-rpcとは
Connectは、サーバとクライアントが用意されており、envoyなどのミドルウェア無しで下記全種類の通信ができる。
- gRPC
- gRPC-WEB
- gRPC-Gateway
注)ブラウザからの場合、gRPCでは通信できない
サーバとクライアントで独立したモジュールなので、クライアントだけConnectを使うなどできる。
余談だが、ConnectのWebクライアントはデフォルト gRPC-Gateway
推しっぽいので、Quic Startの記述をそのまま使うと gRPC-WEB
では通信できない。
変更したい場合createConnectTransport
を createGrpcWebTransport
に変えれば良い。
Envoy不要のIn-process Proxy
In-process Proxy
とは、各言語のgRPCサーバ内でgRPC-WEBも扱えるように変換する処理のこと。
Envoyのなどのミドルウェアによる変換層を挟まなくて済むため、シンプルかつ分かりやすくなる。
もし、Envoyが無いとgRPC-WEBが使えないと思っていた場合、自分の使用している言語に変換用のライブラリがないか?確認してみると良い。
ContentTypeについて
application/grpc-web
例: application/grpc-web+[proto, json, thrift]
送信者は常にメッセージ形式を指定する必要があります(例:+proto、+json)
受信側は、Content-Type にメッセージ形式が指定されていない場合 (「application/grpc-web」など)、デフォルトが「+proto」であると想定する必要があります。
application/grpc-web-text
「application/grpc-web」のテキストエンコード(base64)されたストリーム
例: application/grpc-web-text+[proto, thrift]
こちらも指定なしだとデフォルトはprotoで、protoをbase64したものになる。
余談(Thrift)
ThriftはApache Thriftのことで、元々 Facebook(Meta) で開発されたもの。
gRPCと似た機能を提供している(IDLもある)
Apache Thrift は、言語に依存しない方法でインターフェースを定義し、データをシリアル化することで、分散システムにおける言語とプラットフォームの異種性の問題を解決します。
これにより、チームは好みのプログラミング言語で作業しながら、マイクロサービスとクライアント間の効率的な通信が可能になり、スケーラブルなクロスプラットフォーム アプリケーションの構築に最適な選択肢となります。
ブラウザではgRPCのネイティブ通信ができないし今後もできるようにはならなそう(後述するwhatwg/streamsで出来そう)
ブラウザ方針として、JavaScriptからのHTTP通信は1.1と2を意識させずに透過的に扱う方針。
そのため、HTTP/2に固定する機能は提供したくないっぽい。
whatwg/streamsがブラウザに実装されればgRPC-WEBはオプションになる
2024現在から2年以内に、whatwg/streams
が完成して、ブラウザに実装される予定らしい。
そちらが使えるようになれば、ブラウザ側からgRPCをネイティブかつ、全パターンの通信が扱えるようになるらしい。
最後に
whatwg/streams
があってgRPCのネイティブ通信ができるようになったら、この記事も不要になるなぁと思った次第。。。
参考記事
[gRPC] Connectについて 作られた背景(概要+α) と チュートリアル+αを通して基本的な機能を押さえる
Envoy and gRPC-Web: a fresh new alternative to REST
GRPC PHP 1.66.0
grpc-web streaming-roadmap.md
GitHub whatwg/streams
Discussion