#このページで確認できること

実装上の制約: HTTPS の HTTP/2 と ALPN を検証する場合は、TLS で提供される h2 対応ホストを使用します。TLSなしの h2c は対応サーバーと curl --http2-prior-knowledge などでローカル検証できますが、現在の同梱nginx設定は平文HTTP/1.1です。設定末尾のHTTP/2例はHTTPSでのh2を対象にしています。

#HTTP/1.1 との違い

HTTP/1.1HTTP/2
メッセージ形式開始行・ヘッダーはテキスト、ボディは任意のオクテット列バイナリフレーム (HEADERS / DATA / SETTINGS ...)
1コネクションの並列性応答順序に制約。主要ブラウザは通常パイプライン化しないストリームによる多重化
ヘッダー各メッセージでテキスト形式のフィールドを送信HPACK のインデックス表現・リテラル表現で圧縮
プロトコル選択HTTPSではALPNの http/1.1、平文HTTPではHTTP/1.1として開始HTTPSではALPNの h2。TLSなしのh2cも定義
HoLブロッキング応答順序の制約で発生HTTPストリーム間に共通の応答順序制約はない。TCPレイヤーには残る

#ALPN によるプロトコルネゴシエーション

HTTP/2 over TLS では、TLSハンドシェイク中の ALPN (Application-Layer Protocol Negotiation) 拡張でプロトコルを決定します。クライアントが候補リスト(h2, http/1.1)を提示し、サーバーが1つ選びます。TLS 1.3 では、ECHを使わない場合のクライアント提示値は ClientHello で観測できますが、サーバー選択値は暗号化された EncryptedExtensions 内に入ります。curl や openssl s_client はエンドポイントとして復号後の選択結果を表示できます。

HTTP/2 over TLS 1.3 — ALPN ネゴシエーションと多重化
participants: Client, Server
Client -> Server: TCP 3-way handshake
Client -> Server: ClientHello (ALPN offer: h2, http/1.1)
Server --> Client: ServerHello (key_share)
Server --> Client: EncryptedExtensions (ALPN selected: h2) [encrypted]
note: TLS handshake 完了 — 以降は h2 バイナリフレーム
Client -> Server: Connection preface (PRI * HTTP/2.0)
Client <-> Server: SETTINGS frames
Client -> Server: HEADERS frame (stream 1: GET /)
Client -> Server: HEADERS frame (stream 3: GET /style.css)
Server --> Client: HEADERS + DATA (stream 1)
Server --> Client: HEADERS + DATA (stream 3)
note: stream 1 と 3 が同一コネクション上で並列に流れる
h2c (HTTP/2 over cleartext): TLSなしのHTTP/2。Upgrade: h2c または事前知識(prior knowledge)で開始します。主要ブラウザでは一般に利用できませんが、gRPCのローカル検証(grpcurl -plaintext)などで登場します。curl では --http2-prior-knowledge で試せます。

#ストリーム / フレーム / HPACK

#検証コマンド

curl — HTTP/1.1 と HTTP/2 の比較
$ curl -I --http1.1 https://example.com/
$ curl -I --http2 https://example.com/
# h2がネゴシエートされた場合、curlの表示は「HTTP/2 200」になる

$ curl -v --http2 https://example.com/ 2>&1 | grep -E 'ALPN|HTTP/2'
# 「ALPN: server accepted h2」「using HTTP/2」が見える
openssl — ALPN を直接確認
$ openssl s_client -alpn h2 -connect example.com:443 </dev/null 2>/dev/null \
    | grep -i alpn
# ALPN protocol: h2 と表示されればサーバーは h2 対応
出力例 (curlのバージョンで表示は異なる)
* ALPN: curl offers h2,http/1.1 * ALPN: server accepted h2 * using HTTP/2 * [HTTP/2] [1] OPENED stream for https://example.com/ * [HTTP/2] [1] [:method: GET] * [HTTP/2] [1] [:scheme: https] * [HTTP/2] [1] [:authority: example.com] * [HTTP/2] [1] [:path: /] < HTTP/2 200

#観測ポイント

curl-v で ALPN 交渉結果と疑似ヘッダー(:method 等)を確認。h2cは、対応するローカルサーバーに対して --http2-prior-knowledge で試す。
DevToolsNetwork の Protocol 列(h2)。同一オリジンへのリクエストが同じ接続に多重化された場合、DevToolsのバージョンによっては2回目以降に新規の Connection Start が表示されない。
Proxyプロキシが h2 を終端し、上流に HTTP/1.1 で接続する構成もある。クライアント側 h2 / サーバー側 http/1.1 のような非対称構成を意識して観測。
tcpdump / WiresharkECHを使わない接続では ClientHello の ALPN 提示値を平文で観測できる(フィルタ: tls.handshake.extensions_alpn_str)。TLS 1.3 のサーバー選択値とHTTP/2フレームの解析には、通常SSLKEYLOGFILEによる復号が必要(フィルタ: http2)。
TLS inspectionインスペクション装置が h2 を通すか、http/1.1 にダウングレードするかは製品差が大きい。ALPN の提示値と選択値を direct / 経由で比較すると経路差を把握しやすい。
Wireshark フィルタ例
# ECH未使用時の ClientHello にある ALPN 提示値を抽出(復号不要)
tls.handshake.extensions_alpn_str

# 復号済みなら HTTP/2 フレームを表示
http2

#次のステップ

HTTP/2 でも TCP パケットロス時には、欠落箇所以降のバイトが届くまで全ストリームの配送が待たされる「TCPレベルの Head-of-line blocking」が残ります。異なるストリーム間の配送停止を避けるために QUIC と HTTP/3 が設計されました。また、HTTP/2 の多重化とtrailersを活用したRPCが gRPC です。