gRPC over HTTP/3のプロポーザルと、実装が出てきています。
- プロトコル仕様: https://github.com/grpc/proposal/blob/master/G2-http3-protocol.md
- 実装 (.NET6): https://devblogs.microsoft.com/dotnet/http-3-support-in-dotnet-6/
今回は、仕様を眺めつつ、Ubuntuで実装を動かすところまで試してみようと思います
プロトコル仕様
プロポーザルは、現在 "In review" の状態となっています。
github.com
HTTP/3の基本
HTTP/3はHTTP/2と機能上は大きな違いはありません。HTTPリクエストで通信が始まり、各HTTPリクエスト・レスポンスはQUICのストリームによって多重化されます。そのため、gRPC over HTTP/2にほぼマッピングされます。
HTTP/3では、トランスポートとしてQUICを使用します。これにより、ストリームが異なる場合は、パケットロスやパケットの順番が入れ替わったとしても、受信した後続のパケットを処理しすることができます。
(TCPでは一般的にOSがそのパケットを回復するまで、後続のパケットを受信してても処理できません)
また、各コネクションはコネクションIDによって識別されるため、IPアドレスやポート番号が変わってもコネクションを維持することができます。
(サーバ間の通信が主なgRPCではそのメリットは少ないかもしれない)
一方で、QUICの通信は暗号化されます。暗号化しない手段は提供されていません。QUICは通信の中で、TLSハンドシェイク相当の処理を行うので、一般にはサーバ証明書が必要になります。
なお、1度通信した相手とは、0-RTTハンドシェイクを行うことで、コネクションの確立を早く行えます。
おまけ: QUIC及びHTTP/3の詳細
QUIC及びHTTP/3の詳細については、過去にガッツリ解説を書いたのでこちらを参照ください
asnokaze.hatenablog.com
gRPC over HTTP/3のプロトコル仕様
ここでは、gRPC over HTTP/2 との違うところ/同じところを簡単にかいつまんで紹介します。
- ストリームID: HTTP/3ではQUICにより提供されるストリームを使用しますが、HTTP/2のストリームIDと同じように機能する
- データフレーム: DATAフレームの使い方は、HTTP/3では変更されない
- エラーコード: HTTP/3では、HTTP/2と異なるエラーコードをもちます。そのため、gRPCのエラーコードと改めてマッピングが行われます(仕様参照)
- 接続の管理: GOAWAYフレームおよび、PINGフレームはHTTP/2のときと同様に使用できます。
そのため細かい点はありますが、HTTP/3とHTTP/2を変換するProxyを間に挟むだけで、プロトコル上は問題なく動作すると思われます。
また、HTTP/2はTCPであり、HTTP/3はQUIC(UDP)ですのでどちらで通信を行うのかうまく選択しなければなりません。その戦略には、一般的に2種類の方法があります。
実装
Ubuntu 20.04で、.Net core6 を[公式ドキュメント]にそってインストールします。
その後、下記のexampleを実行します。このGreeterは、nameをつけてSayHelloすると、「hello name」と返してくれます。
github.com
サーバ
.Net 6.0でビルドして、HTTPSと証明書の設定をし、ドキュメントに沿ってHTTP/3を有効化します。
要所としてはこんな感じで動きました。
webBuilder.ConfigureKestrel(options => { X509Certificate2 cert = new X509Certificate2("/path/to/server.pfx"); options.ListenLocalhost(5001, o => { o.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; o.UseHttps(cert); }); });
その後、ビルドして起動するだけです。
動作確認
今回は単純に、HTTP/3に対応したcurl で grpcなリクエストを投げ込んで動作確認しました
クライアントログ (出力はgrpc形式のバイナリがかえってきています)
$ hexdump -C ./req.bin 00000000 00 00 00 00 0f 0a 0d 47 72 65 65 74 65 72 43 6c |.......GreeterCl| 00000010 69 65 6e 74 |ient| 00000014 $ cat ./req.bin | curl -sS -X POST --data-binary @- https://localhost:5001/greet.Greeter/SayHello -k -H"content-type:application/grpc" --http3 -o - | hexdump -C 00000000 00 00 00 00 15 0a 13 48 65 6c 6c 6f 20 47 72 65 |.......Hello Gre| 00000010 65 74 65 72 43 6c 69 65 6e 74 |eterClient| 0000001a
サーバ側ログ
:~/work/grpc-dotnet/examples/Greeter$ ./Server/bin/Debug/net6.0/Server ... info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/3 POST https://localhost:5001/greet.Greeter/SayHello application/grpc 20 info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] Executing endpoint 'gRPC - /greet.Greeter/SayHello' info: Server.GreeterService[0] Sending hello to GreeterClient info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'gRPC - /greet.Greeter/SayHello' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] Request finished HTTP/3 POST https://localhost:5001/greet.Greeter/SayHello application/grpc 20 - 200 - application/grpc 6.2228ms
一旦、ちゃんと動いてそう。
最後に
他の言語でも繋いでみたいですね(冬休みの宿題)