5月 072019
HTTP/2 理解メモ同様に、自作プロキシ (Nekoxy2) で実装した際の HPACK (RFC7541) への自分なりの理解をまとめたものです。
解説とかは他サイトのほうが良いかと。
HPACK は RFC の Appendix に具体例がたくさん載っているので、分からなくなったらそこを眺めると良いでしょう。
※RFC7541 ではバイト数は厳密に「オクテット数」と表現されているが、ここではバイト数とオクテット数は同じ意味とする
目次
HPACK の対象
HTTP/2 での HTTP ヘッダーフィールドは、ヘッダーフレーム内の Header Block Fragment にてやり取りされます。
この Header Block Fragment を結合して得られるバイナリーデータのフォーマットと、その処理方法が HPACK の対象です。
HPACK のエンコーダー/デコーダーにより、HTTP メッセージのヘッダーリストと、ヘッダーブロックのバイナリーデータの相互変換が可能になります。
アーキテクチャ
- HPACK は、HTTP/2 コネクション単位で過去に送受信したヘッダーフィールドの情報を保持しており、ステートフルである
- 送受信したヘッダーフィールドの情報は動的テーブルに保存される
- 動的テーブルはエンコーダーとデコーダー(つまりリクエストとレスポンス)でそれぞれ独立している [RFC7541 2.2]
- 頻出するヘッダーフィールドは静的テーブルに事前に定義されている → RFC7541 Appendix A
- 事前定義や送信済みで既知のヘッダーフィールドは、テーブルインデックスで指定することでデータ量を削減している
- 文字列リテラルはハフマン符号により圧縮することができる(しないこともできる)
- HPACK では HTTP メッセージ内のヘッダーリストの順序は維持される [RFC7541 2.1]
- HTTP/2(RFC7540) では順序への言及はない
- HTTP/1.1(RFC7230) ではヘッダー名が異なるもの同士では順序に意味はないとされている [RFC7230 3.2.2]
- HTTP/2 は 1.1 と意味論的には同じとされているので、順序に関しても同様であると考えるのが妥当か
インデックステーブル
- 静的テーブルと動的テーブルのインデックスは結合され、一つのアドレス空間として扱われる [RFC7230 2.3]
- インデックス1~61 → 静的テーブル
- インデックス62~ → 動的テーブル
- 動的テーブルは FIFO リストである [RFC7541 2.3.2]
- 動的テーブルのサイズは最大値がある [RFC7541 4.2]
- 初期値は HTTP/2 の SETTINGS フレームにて設定される [RFC7541 4.2, RFC7540 6.5.2]
- ヘッダーブロック内に含まれる動的テーブルサイズ更新データにより適宜変更される [RFC7541 4.2, 6.3]
- 動的テーブルのサイズは、以下の通り計算される [RFC7541 4.1]
- (ヘッダー名のバイト数 + ヘッダー値のバイト数 + 32) * エントリー数
- ハフマンエンコーディングはされていない状態の長さで計算される
- 動的テーブルサイズが変更された場合と、新しいエントリーが追加された場合は、最大サイズに収まるよう古いエントリーが削除される [RFC7541 4.3, 4.4]
- 空になることもある [RFC7541 4.4]
- 追加するエントリーは、削除されるエントリーを参照する場合もある点に注意して実装すること [RFC7541 4.4]
ヘッダーブロック フォーマット
参照: RFC7541 6
- ヘッダーブロックは、ヘッダーフィールドのバイナリ表現か動的テーブルサイズ更新データが連結され格納されている
- インデックスヘッダーフィールド表現 → インデックスだけ持つ表現
- リテラルヘッダーフィールド表現(3種) → 新しいヘッダー値を持つ表現
- 動的テーブルサイズ更新 → 新しい動的テーブルサイズを通知する表現
- 各バイナリ表現の種類は、1 byte 目の数 bit プレフィックスで判定される
- 長さの情報は下記プリミティブ型表現にて指定されているので、順次読み取っていけば分かる
種類 | インデックス更新 | 別表現への変更 |
---|---|---|
インデックスヘッダーフィールド表現 | × | ○ |
インデックス更新を伴う リテラルヘッダーフィールド表現 | ○ | ○ |
インデックス更新を伴わない リテラルヘッダフィールド表現 | × | ○ |
インデックスされない リテラルヘッダフィールド表現 | × | × |
動的テーブルサイズ更新 | – | – |
リテラルヘッダフィールド表現
- ヘッダー名の指定は、インデックスされた名前の場合と、新しい名前の場合がある
- インデックスが 1 以上の場合はインデックスされた名前
- インデックスが 0 の場合は新しい名前
- 次バイトに新しい名前が続く
プリミティブ型表現
参照: RFC7541 5
- HPACK には符号なし可変長 整数表現と文字列表現がある
整数表現
参照: RFC7541 5.1
- 1 byte 目には、ヘッダーブロック フォーマットを判定する数 bit のプレフィックスが含まれる
- 残りの bit に収まる数値の場合は、その 1 byte で表現が完了する
- 収まらない場合は 1 で埋め、その数値を引いた残りを 7bit/byte のリトルエンディアンで繋げ、表現する
- 7bit/byte なのは先頭 1 bit が 1 なら継続、0 なら最終バイトを示すため
文字列表現
参照: RFC7541 5.2
- 1 bit 目にハフマンエンコーディング有無フラグがある
- 残ビットは整数表現にて文字列の長さ(バイト数)が表現される
- 整数表現であるので、上記の通り1バイトに収まらない場合もある
- 長さの後は、指定された長さの文字列が続く
- ハフマンエンコーディング有無フラグが 0 の場合は単なる ASCII エンコーディング
- 1 の場合はハフマン符号により圧縮されたバイト配列となる
- HPACK はカノニカルハフマンで、ハフマンコード表は RFC7541 Appendix B に記載されている