Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

この記事で伝えたいこと

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/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)。

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away