Skip to content

Instantly share code, notes, and snippets.

@masakielastic
Last active March 5, 2026 05:11
Show Gist options
  • Select an option

  • Save masakielastic/5e32c3f7865f5fab2e8db93320eb4ad2 to your computer and use it in GitHub Desktop.

Select an option

Save masakielastic/5e32c3f7865f5fab2e8db93320eb4ad2 to your computer and use it in GitHub Desktop.
PHP 最小 HPACK:静的テーブル+表現パース/生成のみ

PHP 最小 HPACK:静的テーブル+表現パース/生成のみ

PHP で HTTP/2 の HPACK を最小実装。目的は HTTP/2 ヘッダーブロックの decode/encode を最小限に扱えるようにすることです。

スコープ(必須)

HPACK の静的テーブル(Static Table)だけを扱うこと

Dynamic Table は実装しない

サイズ更新(Dynamic Table Size Update)も未対応

インデクシングに伴う追加・退避も未対応

Huffman 符号化は未対応(常に Huffman=0 として扱う)

decode で Huffman=1 が来たら例外(Unsupported)でよい

ヘッダーフィールド表現(Header Field Representation)の parse と generateを実装する 具体的には次を扱う(HPACK RFC の表現種別):

Indexed Header Field Representation(先頭ビット 1xxxxxxx)

Literal Header Field without Indexing(0000xxxx)

Literal Header Field never Indexed(0001xxxx) ※ with indexing(01xxxxxx)は decode だけ Unsupported でも良い(encode は不要)

スコープ外(やらない)

Dynamic table への追加・参照、eviction、サイズ計算

Huffman の decode/encode

HPACK 以外(HTTP/2 フレーム処理など)

期待する成果物

1) PHP コード(単体で動く)

src/Hpack/Decoder.php

src/Hpack/Encoder.php

src/Hpack/StaticTable.php

src/Hpack/Integer.php(HPACK integer representation: N-bit prefix)

src/Hpack/StringLiteral.php(Huffman=0 の文字列リテラル)

src/Hpack/Exceptions.php(Unsupported 等)

tests/(phpunit でも素の assert でも良い)

Composer 前提でも、単体スクリプト実行でもよいが、外部依存は極力なしで。

2) 公開 API(提案)

Hpack\Decoder::decode(string $headerBlock): array

戻り値は [['name' => string, 'value' => string], ...] の配列(順序保持)

Hpack\Encoder::encode(array $headers): string

入力は [['name'=>..., 'value'=>...], ...] または ['name' => 'value'] でも良い(どちらかに統一)

encode は最小でよい。基本方針:

(name,value) が静的テーブルに完全一致するなら Indexed を出す

(name) だけ一致するなら Literal (without indexing) with indexed name を出す

それも無ければ Literal (without indexing) with new name を出す

文字列は Huffman=0 固定

実装要件(重要)

Integer 表現(HPACK Integer Representation)

decodeInteger(string $data, int $offset, int $prefixBits): array{int $value, int $nextOffset}

encodeInteger(int $value, int $prefixBits, int $prefixMaskBase): string

prefixBits: 5, 6, 7 のケースが出る

例: Indexed は prefix=7(先頭 1bit 以外が prefix)

不正入力(バッファ不足、継続バイト不足、過大シフトなど)は例外

String Literal(Huffmanなし固定)

先頭ビット: Huffman flag(MSB)。1なら Unsupported

長さは prefix=7 integer

その後ろに length バイトの生文字列(バイナリ安全に扱う)

decode で不足したら例外

Static Table

HPACK の static table を 1-indexed で定義

lookup:

getByIndex(int $i): array{name:string,value:string}

findIndexByPair(string $name, string $value): ?int

findIndexByName(string $name): ?int(最小 index を返すでよい)

デコードの仕様(最低限)

Decoder::decode() は headerBlock を先頭から読み、表現に応じてヘッダーを配列に追加する。

Indexed:

index=0 は invalid(例外)

index が static table 範囲外なら Unsupported(dynamic 未実装のため)

Literal without indexing / never indexed:

name が indexed か new name かを判定して読む

value は string literal

Dynamic Table Size Update(001xxxxx)が来たら Unsupported(今回 scope 外)

エンコードの仕様(最低限)

可能なら Indexed を使う

それ以外は Literal without indexing を使う

never indexed は encode では基本使わない(オプションでフラグ指定があってもよい)

出力はバイナリ文字列(PHP の string)

テスト(必須)

少なくとも以下のテストを用意してください(ベタな assert で可):

Integer decode/encode: prefix=5/6/7 の境界(0, maxPrefix-1, maxPrefix, maxPrefix+1, 大きめ)

String literal decode/encode: 空文字、ASCII、バイナリ含む(\x00 等)、不足時例外

Indexed の decode: static table の典型例(:method: GET など)

Literal without indexing(indexed name)の decode/encode

Literal without indexing(new name)の decode/encode

Huffman=1 を含む string literal を decode したら Unsupported

Dynamic table update を見たら Unsupported

不正 index=0 は例外

バッファ不足系(途中で切れた header block)例外

コーディング指針

すべての関数は バイナリ安全(strlen, ord, substr を前提に注意)

例外メッセージはデバッグしやすく(offset, kind を含める)

strict_types=1

PHP 8.2+ を想定

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment