5月 072019
 
Pocket

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 に記載されている

 Leave a Reply