Edited at
http2Day 2

HTTP/2 GOAWAY, その使い方

More than 3 years have passed since last update.


Go away, and never come back

--- a creature living in cave


HTTP/2 には GOAWAY フレームがあります. RFC 7540 によると接続を切る場合には送信せよとあります. ペイロードにはピアが開始したストリーム ID で何かしら処理をしたものの最大値を設定します (last-stream-id). このようなものを一体何のために使うのでしょうか.

HTTP/2 では一接続上にたくさんのリクエストをのせることができます. 基本的に持続的接続となって HTTP/1.1 のときよりも接続時間は長くなることを想定しています. 接続はタイムアウトによって切られることもありますが, 自発的に切る場合もあります. 自発的に切る場合の例としてはコネクションエラーなど致命的なエラーで続行不可能になるケース, とサーバーのメンテナンスやロードバランシングの目的などで新たに接続をしてほしい, とピアに伝えるようなケースがあります.

どちらもこの接続はもう<すぐ>終わりだよ, というシグナルですが, これを受け取ったエンドポイントにとって気になるところはどのリクエストまで処理した (中途半端な状態も含めて) のでしょうか, ということです. 処理が途中あるいは完全に処理されたリクエストについては, それが冪等性のあるメソッド (e.g., GET) 以外の場合, 新しい接続上で再度リクエストして安全かどうかはわかりません. last-stream-idは, どのリクエストは安全に再試行できるかどうかをピアに伝える役を果たすのです. last-stream-id 以下のリクエストで冪等性のないメソッドについては再試行は無理です, と RFC に書かれています.

エラーで自発的に接続を切る場合は, 処理 (途中) の最大のストリーム ID を含んだ GOAWAY を送信して送信終わったら接続を切ればいいです. ピアの GOAWAY を行儀よく待つ必要はほとんどありません. なぜならこのエラーはほとんどの場合, ピアのプロトコル実装のバグが原因で起こるものであり, バギーな実装に何を期待することもできないからです.

メンテナンスやロードバランシング目的で GOAWAY を使う場合は少し注意深くなる必要があります. HTTP/2 は非同期プロトコルなのです. GOAWAY を受信したエンドポイントは新たにストリームを開くことは許可されていません. 問題は GOAWAY を送信してからピアに届くまでの間にすでに新しいストリームを開く HEADERS が飛行中かもしれないし, 遅延の間にピアはどんどん HEADERS フレームを生成しているかもしれません. もちろん last-stream-id の値を送信しているのでクライアントは新しい接続でこれら迷子のストリームの面倒を見ることはできるのですが, もっとうまくやる方法が RFC に書かれています.

これは graceful shutdown のやり方です. GOAWAY を送信するエンドポイントをサーバーとしましょう. サーバーは, (話の間が持たないので) そろそろ接続を切りたいと思った場合, いきなり現時点での正直な last-stream-id を送らずに 2^31 - 1 という値を last-stream-id として送信します. last-stream-id は 31 bit unsigned integer なのでその最大値です. これはクライアントに "まもなくこの接続でリクエストができなくなる" ということを知らせるという効果があります. そして少なくとも 1 RTT の後, 本気の last-stream-id を持った GOAWAY をクライアントに送信するのです. 前項に書いたものよりも再試行のストリームは減るはずです. サーバーは GOAWAY を送った後も既存ストリームでクライアントと通信しつづけることができます. nghttp2 の nghttpx プロキシーはこの graceful shutdown を実装しています (1 RTT ではなく 1 秒程度待つようになっています).

非同期プロトコルなので GOAWAY を送った後に新たにストリーム HEADERS が届く場合もあります. その ID が last-stream-id よりも厳密に大きい場合, 受信したエンドポイントはまるで何も届かなかったかのように無視せよ, と RFC に書かれています. ID が last-stream-id と同じか小さかった場合はどうするのでしょう. last-stream-id はサーバーが何かしら処理を始めた ID を入れるので前述の 2^31 - 1 以外のケースだとありえないはずですが, 無視してもいいでしょう. 不安なら last-stream-id に入れる値は 2^31 - 1 以外では必ず処理を開始した ID にするように気をつけることです.