この記事で伝えたいこと
HTTP/2 では、「stream」と「frame」と呼ばれる構造を導入することで、複数のリクエストを 1 つの TCP セッションで送受信できるようになりました。この構造の変化に伴い、HTTP/1.1 以前には存在しなかったエラーがあります。
そこで、この記事では
- HTTP/2 で新たに考慮すべきエラーを洗い出し、どう対処すべきなのかを整理します。
- 付録として、HTTP/2 のエラーを活用することで実現した「Request Reliability Mechanism」を説明します。
1. 変わらない部分:ステータスコード
HTTP/1.1 で利用していた「404 Not Found」や「500 Internal Server Error」などのステータスコードは、HTTP/2 にそのまま引き継がれます。したがって、HTTP アプリケーションで発生するエラーは、HTTP/1.1 と同じ考え方でハンドリングできます。
2. HTTP/2 固有のエラー
(HTTP/2 の仕様を理解していることを前提に書いています。仕様の概説は http2 勉強会の資料 を参照ください。1 つの connection の上で複数の仮想的な stream が存在するイメージです。)
2.1. Connection Error
- 発生条件
- frame レイヤの処理をこれ以上続行できない場合
- 接続状態が不正な場合
- エラー発生側がやること
- クライアントに
GOAWAY
フレームを送信するべし (SHOULD) - そして TCP を閉じる (MUST)
- クライアントに
- 対向側でやること
- 受け取った
GOAWAY
フレームの中に、接続を切られた理由を示すエラーコードとデバッグメッセージが格納されている。必要に応じてチェック。 - エラーコードは下記の通り
- リクエストを送りたい場合は connection からやり直す。既存コネクションに新しい stream を作っちゃダメ (MUST NOT)。
- 受け取った
エラーコード (GOAWAY
と RST_STREAM
フレームで使用)
error code | 意味 |
---|---|
0x0 | エラー無し |
0x1 | プロトコルエラー (他の具体的な error code に該当しない場合) |
0x2 | 想定外の内部エラー |
0x3 | フローコントロール違反を検知 |
0x4 |
SETTINGS フレームに対する応答がタイムアウトした |
0x5 | stream が half-close した後に Frame を受信した |
0x6 | frame サイズが不正 |
0x7 | stream を拒否された (HTTP アプリケーションの処理が行われる前) |
0x8 | stream が不要になったなのでキャンセルした |
0x9 | ヘッダ圧縮のコンテキストを継続できなくなった |
0xa |
CONNECT リクエストに対してリセットまたは異常切断が発生 |
0xb | 過負荷を引き起こす挙動を検知 |
0xc | 最小限のセキュリティ要件を満たしていない |
0xd | HTTP/1.1 でリクエストしてください |
2.2. stream エラー
- 発生条件
- 特定の stream でエラーが発生したが、他の stream 処理には影響を与えない場合
- エラー発生側でやること
-
RST_STREAM
フレームを送信する
-
- 対向側でやること
- 受け取った
RST_STREAM
フレームの中に、問題を起こした stream の ID とエラーコードが格納されている。必要に応じてチェック。 - ループを予防するために、
RST_STREAM
に対してRST_STREAM
をレスポンスしてはならない (MUST NOT)。
- 受け取った
2.3. 接続終了 (Connection Termination)
- 発生条件
- stream の状態が open または half-closed にあり、TCP の接続がクローズまたはリセットされたとき
- 検知した側はどうなるか
- 検知した側がクライアントである場合、リクエストの自動リトライができない
- 自動リトライに関しては後述しています。
3. HTTP/2 で判断可能になったリクエストのリトライ安全性
HTTP/1.1 以前では、リクエストを送信した後にエラーが発生した際、サーバ側で処理が全く行われなかったのか、はたまた途中まで処理が行われたのかを判別できませんでした。
HTTP/2 では、「サーバがリクエストを処理していないこと」をクライアント側で判断するための機構が 2 つ存在します。
リクエストが未処理であれば、そのリクエストが変更を伴うものであっても、安全にリトライできます。(リトライの結果、リクエストが失敗するかどうかは別として)
この性質を利用すると、リクエストの自動リトライを機械的に実行できる場合があり、ユーザ体験の向上に寄与する可能性があります。
これはリクエストごとに stream id を採番したことで生まれたメリットです。
もちろん、自動リトライを実施するかどうかは、開発者に委ねられていますし、ロードバランシングしている場合は注意が必要です。
3.1 GOAWAY フレーム
(本件は tatuhiro_t さんの記事でも解説有り)
GOAWAY
フレームには、最後に処理に着手した stream の id (last stream id) が判断できます。stream id は整数値であり、単調増加で採番されます。
したがって、それより大きな stream id のリクエストはサーバがまだ手をつけていないことが保証されます。
3.2 RST_STREAM フレーム
RST_STREAM
フレームのエラーコードの中に 0x7 (Refused Stream) を含めることができるタイミングは、サーバがその stream の処理を行う前であることが RFC で要求されています。
したがって、Refused Stream が返ってきた stream で送信したリクエストは、安全にリトライできると判断できます。
まとめ
HTTP/2 のエラーに関するトピックをまとめてみました。
代表的な実装を実際に眺めてみて、上記以外にどんなエラーハンドリングが存在し、その理由がどんなものなのかについても調べてみたいところです。