blog.syfm

徒然なるままに考えていることなどを書いていくブログ

gRPC-Web Internals

これは Aizu Advent Calendar 2018 の 2 日目の記事です。
1 日目は @Ruto009 さんで、3 日目は @ayanoaizu さんです。

adventar.org

gRPC-Web is GA!

今年の 10 月末に、gRPC-Web がついに GA (Generally Available) となり、メジャーリリースが行われました 🎉

grpc.io

gRPC は、HTTP/2 ベースの RPC フレームワークで、IDL (Interface Definition Language) を元にクライアント・サーバのスタブを各プログラミング言語向けに生成できる、といった特徴を持っています。
gRPC-Web は、gRPC ベースのプロトコルで、gRPC を Web ブラウザ環境から利用するために作られました。

では、Web ブラウザ環境で動作するために、gRPC-Web ではどのような拡張が行われているのでしょうか?
この記事では gRPC と gRPC-Web を比較し、その違いを見ていきたいと思います。

この記事は、今年行われた技術書典 5 で執筆した章「gRPC Web Internals」を元に構成されています。
技術書典からしばらく経ったため、この章のみを一般公開したいと思います。この記事より詳しい解説はそちらの方をぜひご覧ください!

github.com

仕様

gRPC と gRPC-Web の仕様は共に gRPC の GitHub リポジトリ内に存在します。

github.com

gRPC の仕様は以下のディレクトリに、

grpc/PROTOCOL-HTTP2.md at master · grpc/grpc · GitHub

gRPC-Web の仕様は以下のディレクトリにあります。

grpc/PROTOCOL-WEB.md at master · grpc/grpc · GitHub

以下ではこれらの仕様から特に特徴的な箇所を紹介します。

HTTP のプロトコルバージョン

gRPC は、HTTP/2 ベースのプロトコルです。すべての通信は HTTP/2 によって行われ、デフォルトで TLS が有効になっています。ただし、開発時などの都合のために TLS を無効にした非セキュアな通信を行うこともできます。例えば、Go がクライアント・サーバの場合、WithInsecure オプションを使うことで実現可能です。

それに対し、gRPC-Web は状況に応じて HTTP/1 または HTTP/2 のいずれかが使用されます。ほとんど全ての Web ブラウザでは HTTP/2 での通信において TLS が必須となっているため、そういった場合は HTTP/1 による非セキュアな通信にフォールバックされます。

また、HTTP/2 はバイナリベースのプロトコルであるのに対し、HTTP/1 はテキストベースのプロトコルです。gRPC のデフォルトのシリアライザである Protocol Buffers 3 によってシリアライズされたデータを HTTP/1 で扱うためには、その内容をテキストとして扱えなければいけません。そのため、gRPC-Web では Base64 をデフォルトのエンコーダ / デコーダとして規定しています。実際、gRPC-Web による通信をブラウザから見てみると Base64 エンコードされていることが分かります。

f:id:ktr_0731:20181130215714p:plain
Base64 エンコードされたレスポンスボディ

gRPC-Web のデザインゴールの一つとして、「HTTP/1 で代替できない、HTTP/2 特有のフレームや振る舞いを分離する」といったものがあります。
例えば、gRPC の仕様 で使用されている GOAWAY フレームや、PING フレームを HTTP/2 での通信でも使用せずに、HTTP/1 のセマンティクスのみで表現できるようになっています。

RPC 方式

gRPC には、

  • Unary RPC
  • Server streaming RPC
  • Client streaming RPC
  • Bidirectional streaming RPC

の 4 種類の RPC 方式があります。
Unary RPC は一つのリクエストに対して、一つのレスポンスが返り、Server streaming RPC は一つのリクエストに対して、複数のレスポンスが返ります。
Client streaming RPC は Server streaming RPC とは逆に、複数のリクエストに対してただ一つのレスポンスが返ります。
Bidirectional streaming RPC は複数のリクエストに対して複数のレスポンスが返ります。

Unary / Server streaming RPC

これら二つの RPC 方式は gRPC-Web でもサポートされています。レスポンスボディは Base64エンコードされていますが、その中身は通常の gRPC と同様のスキーマになっています。このスキーマABNF で表現されています。
後述する improbable-eng/grpc-web では、トレイラーヘッダ名が lower-case header ではなく MIME header 形式になっているという、gRPC / gRPC-Web の仕様から外れた実装がありましたが、v0.6.4 で修正されています。

Client / Bidirectional streaming RPC

現在、これらの RPC 方式は gRPC-Web の仕様では 定義されていません。ただし、grpc/grpc-web にあるロードマップによると、将来的に WebSocket を使った実装を追加する可能性があるといった記述がありますが、これはディスカッションで提案されているもので、仕様の一部ではありません。

バイナリシリアライザからの切り替え

gRPC では Protocol Buffers や FlatBuffers のようなバイナリベースのシリアライザが使用されていますが、これらはクライアントがスキーマを把握するために巨大なメタデータを必要とします。また、Web ブラウザ上でのデシリアライズも CPU 負荷が大きくなりやすいため、Gmail の特殊な JSON のように軽量で効率的なテキストベースの形式に切り替えていくことを検討しています。

その他の gRPC との違い

他にもいくつかロードマップに挙げられている機能があります。詳しくはリンク先を見てみてください。

gRPC-Web の実装たち

ここまで、gRPC-Web の仕様をかいつまんで見てきました。では次に実際の実装はどのようなものがあるか見てみましょう。
現在、主もアクティブな実装が二つ存在します。一つ目が gRPC オーガニゼーションに属している grpc/grpc-web、もう一方が Improbable 社のエンジニアを中心に開発されている improbable-eng/grpc-web です。

github.com

github.com

これらのクライアント・サーバ実装の互換性を知るには以下のリポジトリが役に立ちます。

github.com

プロキシ

gRPC-Web は gRPC の拡張プロトコルであるため、クライアント・サーバ間でその差異を吸収するためのレイヤーが必要となります。これにはいくつかの方法があります。

grpc/grpc-web

このリポジトリには、JavaScript および TypeScript のクライアントを生成するための Protocol Buffers プラグインおよびプロキシ関連のファイルが含まれています。
プロキシには以下が使用可能です。

improbable-eng/grpc-web

このリポジトリにも grpc/grpc-web と同様に JavaScript / TypeScript クライアント向けのプラグイン、および Go 向けのサーバライブラリなどが含まれています。
プロキシには以下が基本的に使用可能です。

  • Envoy
  • grpcwebproxy

また、improbable-eng/grpc-web では grpcweb という Go パッケージが提供されており、プロキシを使わずに、grpc.Server をラップすることで gRPC ⇔ gRPC-Web の相互変換を行うことができます。

improbable-eng/grpc-web の最も特筆すべき点として、Client / Bidirectional streaming RPC を独自に実装していることが挙げられます。
この実装には、WebSocket が使われています。
そのため、improbable-eng/grpc-web を使うことで、これらの RPC も扱うことができるようになり、ネイティブの gRPC と同等の機能性を得ることができるようになります。
ただし、Envoy や NGINX などのプロキシを使うことができなくなる点に注意してください。
また、前述の通り、WebSocket を使用した実装は仕様にはない拡張であるため、今後 grpc/grpc-web で実装されるとも限りません。

gRPC-Web の gRPC サポート

将来的に、WHATWG Fetch / Streams が多くのモダン Web ブラウザで使用可能になった段階で gRPC-Web はネイティブの gRPC プロトコルをサポートすることが検討されています。
ただし、以下の各 Web ブラウザのサポート状況を見る限り、当分先になりそうな気がします。

developer.mozilla.org

developer.mozilla.org

gRPC-Web がネイティブ gRPC プロトコルとの通信をできるようになれば、当然 Client / Bidirectional streaming も可能になります。
gRPC-Web / gRPC の違いを意識せずに透過的に利用できるようになるにはここまで成熟する必要があるでしょう。

宣伝

対話的にサーバと通信ができる gRPC クライアントの Evans を作っています。

github.com

Evans は gRPC-Web をサポートしているので、通常の gRPC サーバと同様にテストすることができます! 多分汎用 gRPC クライアントで gRPC-Web をサポートしているのは Evans だけだと思います。

ぜひ使ってみてください 🙏