HTTP/2 のエラーハンドリングと Request Reliability Mechanism

# この記事で伝えたいこと
HTTP/2 では、「stream」と「frame」と呼ばれる構造を導入することで、複数のリクエストを 1 つの TCP セッションで送受信できるようになりました。この構造の変化に伴い、HTTP/1.1 以前には存在しなかったエラーがあります。

そこで、この記事では

1. HTTP/2 で新たに考慮すべきエラーを洗い出し、どう対処すべきなのかを整理します。
2. 付録として、HTTP/2 のエラーを活用することで実現した「Request Reliability Mechanism」を説明します。

# 1. 変わらない部分：ステータスコード
HTTP/1.1 で利用していた「404 Not Found」や「500 Internal Server Error」などの[ステータスコード](http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml)は、HTTP/2 にそのまま引き継がれます。したがって、HTTP アプリケーションで発生するエラーは、HTTP/1.1 と同じ考え方でハンドリングできます。

# 2. HTTP/2 固有のエラー
(HTTP/2 の仕様を理解していることを前提に書いています。仕様の概説は [http2 勉強会の資料](http://www.slideshare.net/YoshihiroIwanaga/http2-35067536) を参照ください。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](https://tools.ietf.org/html/rfc7540#section-5.1) した後に Frame を受信した |
| 0x6 | frame サイズが不正 |
| 0x7 | stream を拒否された (HTTP アプリケーションの処理が行われる前) |
| 0x8 | stream が不要になったなのでキャンセルした |
| 0x9 | ヘッダ圧縮のコンテキストを継続できなくなった |
| 0xa | `CONNECT` リクエストに対してリセットまたは異常切断が発生 |
| 0xb | 過負荷を引き起こす挙動を検知 |
| 0xc | [最小限のセキュリティ要件](https://tools.ietf.org/html/rfc7540#section-9.2)を満たしていない |
| 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 さんの記事](http://qiita.com/tatsuhiro-t/items/d9322b312f63488d6493)でも解説有り)

`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 のエラーに関するトピックをまとめてみました。
代表的な実装を実際に眺めてみて、上記以外にどんなエラーハンドリングが存在し、その理由がどんなものなのかについても調べてみたいところです。