RFC 7540 HTTP/2ストリーム多重化の解説

Tech

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

RFC 7540 HTTP/2ストリーム多重化の解説

背景

Hypertext Transfer Protocol (HTTP) は、World Wide Webの基盤をなすアプリケーション層プロトコルです。その初代バージョンであるHTTP/1.xは、シンプルなテキストベースのプロトコルとして広く普及しましたが、Webコンテンツの複雑化・リッチ化に伴い、いくつかの性能上の課題を抱えるようになりました。特に顕著だったのが、Head-of-Line (HOL) Blockingという問題です。HTTP/1.xでは、TCP接続ごとに一度に一つのリクエストしか処理できず、前のリクエストの処理が完了するまで次のリクエストがブロックされるため、応答時間の遅延を引き起こしていました。この問題は、複数のTCP接続を張ることで部分的に緩和されましたが、接続数の増加はTCPのオーバーヘッド増大や輻輳制御の非効率性といった新たな問題を生みました。

これらの課題を解決するため、Googleが開発したSPDYプロトコルを基盤として、新たなHTTPプロトコルであるHTTP/2が策定されました。その仕様は、2015年5月にRFC 7540として公開され、Webのパフォーマンス向上に大きく貢献しました。その後、2022年6月にはRFC 9113によってRFC 7540は廃止されましたが、その中心的な概念であるストリーム多重化の仕組みはHTTP/2の根幹として引き継がれています。

設計目標

RFC 7540で定義されたHTTP/2の主要な設計目標は以下の通りです。

  • 単一TCP接続での多重化: 複数のリクエストとレスポンスを単一のTCP接続上で並行して処理し、接続確立のオーバーヘッドとリソース消費を削減する。

  • HOL Blockingの解消: アプリケーション層でのHOL Blockingを緩和し、複数のリソースを効率的に取得できるようにする。

  • ヘッダ圧縮: リクエストおよびレスポンスのヘッダを効率的に圧縮し、ネットワーク帯域の消費を抑える(HPACK)。

  • サーバープッシュ: クライアントが明示的にリクエストする前に、サーバーが必要と判断したリソースをプッシュする機能を提供し、ページの読み込みを高速化する。

  • 優先度制御: リソースの重要度に応じて、サーバーが送信するデータの優先順位を制御できるようにする。

  • バイナリフレーミング: HTTPメッセージをバイナリ形式のフレームに変換することで、解析効率と堅牢性を向上させる。

これらの目標の中心にあるのが「ストリーム多重化」であり、HTTP/2の性能向上を支える最も重要な技術要素です。

詳細

HTTP/2のストリーム多重化は、単一のTCP接続上で複数の論理的な「ストリーム」を確立し、それぞれのストリーム上で独立したリクエストとレスポンスを並行して送受信する仕組みです。

ストリームの概念

ストリームは、HTTP/2接続内で確立される論理的な双方向バイトフローです。各ストリームは一意の31ビットの識別子(Stream Identifier)を持ち、クライアントが開始するストリームは奇数、サーバーが開始するストリームは偶数となります。ストリームは独立してフロー制御され、優先度付けが可能です。

HTTP/2の通信単位:フレーム

HTTP/2では、すべての通信は「フレーム」というバイナリ単位で行われます。HTTP/1.xのようなテキストベースのメッセージとは異なり、HTTP/2メッセージは複数のフレームに分解されて送信されます。

HTTP/2フレーム構造:

+----------------------------------+
| Length: 24 bits                  |  フレームペイロードの長さをバイト単位で示す。
+----------------------------------+
| Type: 8 bits                     |  フレームのタイプ(DATA, HEADERS, PRIORITYなど)。
+----------------------------------+
| Flags: 8 bits                    |  タイプ固有のフラグ。
+----------------------------------+
| R: 1 bit                         |  予約済みビット。常に0。
+----------------------------------+
| Stream Identifier: 31 bits       |  このフレームが属するストリームの識別子。
+----------------------------------+
| Frame Payload (Length bytes)     |  フレームの種類に応じたデータ。
+----------------------------------+

DATAフレームはリクエストやレスポンスの本体、HEADERSフレームはHTTPヘッダ、SETTINGSフレームは接続設定、WINDOW_UPDATEフレームはフロー制御に使用されます。

ストリーム多重化の仕組み

単一のTCP接続上で、クライアントとサーバーは同時に複数のフレームを送受信します。これらのフレームは、そのStream Identifierに基づいて、対応する論理ストリームにルーティングされます。これにより、複数のリクエスト(HEADERSフレーム)とそれぞれのレスポンス(HEADERSフレームとDATAフレーム)がTCP接続上で interleaved(混在)して送信されます。

sequenceDiagram
    participant C as クライアント
    participant S as サーバー

    C ->> S: TCP接続確立
    C ->> S: SETTINGS フレーム (初期設定)
    S ->> C: SETTINGS フレーム (初期設定)
    S ->> C: SETTINGS ACK フレーム
    C ->> S: SETTINGS ACK フレーム

    Note over C,S: HTTP/2接続確立完了

    C ->> S: Stream 1: HEADERS フレーム (Req A)
    C ->> S: Stream 3: HEADERS フレーム (Req B)
    C ->> S: Stream 1: DATA フレーム (Req A body)

    S ->> C: Stream 1: HEADERS フレーム (Resp A)
    S ->> C: Stream 3: HEADERS フレーム (Resp B)
    S ->> C: Stream 1: DATA フレーム (Resp A body)
    S ->> C: Stream 3: DATA フレーム (Resp B body)

    Note over C,S: 複数のリクエスト/レスポンスが並行処理

フロー制御

HTTP/2は、接続レベルとストリームレベルの双方でフロー制御メカニズムを提供します。これはWINDOW_UPDATEフレームを用いて行われ、受信側が処理できるデータ量(ウィンドウサイズ)を送信側に通知します。これにより、受信側のバッファオーバーフローを防ぎ、送信レートを調整します。

優先度設定

PRIORITYフレームを使用することで、クライアントは各ストリームの依存関係と重み付けを指定できます。サーバーはこれらの優先度情報に基づいて、限られたリソース(CPU、帯域幅など)をどのストリームに割り当てるかを決定し、重要なリソースの配信を早めることが可能になります。

ヘッダ圧縮 (HPACK)

HTTP/2では、リクエストとレスポンスのヘッダを効率的に圧縮するためにHPACK (Header Compression for HTTP/2) を採用しています。これは、静的テーブルと動的テーブル、ハフマン符号化を組み合わせることで、繰り返し送信されるヘッダフィールドのサイズを大幅に削減します。

HTTP/2フレームが収容されるTCP/IPパケット構造の概念図:

+------------------------------------------------------------------+
| Ethernet Header (14 bytes)                                       |
+------------------------------------------------------------------+
| IP Header (20-60 bytes, e.g., IPv4 Header)                       |
|   Version, Header Length, Type of Service, Total Length, ID, ... |
+------------------------------------------------------------------+
| TCP Header (20-60 bytes)                                         |
|   Source Port, Destination Port, Sequence Number, ACK Number, ...|
+------------------------------------------------------------------+
| HTTP/2 Frame Header (9 bytes)                                    |
|   Length: 24 bits                                                |
|   Type: 8 bits                                                   |
|   Flags: 8 bits                                                  |
|   R: 1 bit                                                       |
|   Stream Identifier: 31 bits                                     |
+------------------------------------------------------------------+
| HTTP/2 Frame Payload (Length bytes)                              |
|   (e.g., DATA, HEADERS, PUSH_PROMISE)                            |
+------------------------------------------------------------------+

HTTP/2フレームは、TCPセグメントのペイロードとしてカプセル化されて送受信されます。

相互運用

既存プロトコルとの比較

HTTP/2は、HTTP/1.xの課題を克服し、Webのパフォーマンスを向上させるために設計されました。さらに、HTTP/2が抱える一部の課題を解決するため、次世代プロトコルとしてHTTP/3が登場しています。

  • HTTP/1.xとの比較:

    • 接続モデル: HTTP/1.xはリクエストごとに新しいTCP接続を確立するか、パイプライン処理(HOL Blockingが顕著)を行います。HTTP/2は単一のTCP接続上で複数のストリームを多重化します。

    • HOL Blocking: HTTP/1.xではTCPレベルおよびアプリケーションレベルでHOL Blockingが発生し、リソースの並列取得が制限されます。HTTP/2はアプリケーションレベルのHOL Blockingを解消しましたが、TCPレベルのHOL Blockingは残ります。

    • データ形式: HTTP/1.xはテキストベースのメッセージを使用します。HTTP/2はバイナリフレーミングレイヤーを導入し、解析効率と堅牢性を高めます。

    • ヘッダ効率: HTTP/1.xは各リクエストで冗長なヘッダを送信します。HTTP/2はHPACKによるヘッダ圧縮を導入し、帯域幅の使用を最適化します。

    • サーバープッシュ: HTTP/1.xにはサーバープッシュ機能がありません。HTTP/2はサーバープッシュをサポートし、クライアントが明示的にリクエストする前に必要なリソースを送信できます。

  • HTTP/3との比較:

    • トランスポート層: HTTP/2はTCP上で動作します。HTTP/3はUDPベースのQUICプロトコル上で動作します。

    • HOL Blocking: HTTP/2ではTCPレベルのHOL Blocking(パケットロスが発生した場合、そのTCP接続上のすべてのストリームがブロックされる)が残ります。HTTP/3/QUICでは、ストリームが独立して配送されるため、TCPレベルのHOL Blockingを完全に解消します。

    • 接続確立: HTTP/2はTLSハンドシェイクとTCPハンドシェイクを合わせた複数のラウンドトリップを必要とします。HTTP/3/QUICはTCPとTLSのハンドシェイクを統合し、0-RTT (Zero Round-Trip Time) Resumptionにより接続確立時間を大幅に短縮できます。

    • 接続マイグレーション: HTTP/2には接続マイグレーション機能がありません。HTTP/3/QUICは、クライアントのIPアドレスやポート番号が変更されても、接続を維持できる接続マイグレーション機能をサポートします。

flowchart TD
    A["HTTP/1.1(\"単一接続\")"] -- |リクエスト1| --> B("サーバー");
    B -- |レスポンス1| --> A;
    A -- |リクエスト2("待機")| --> B;
    B -- |レスポンス2| --> A;
    subgraph HTTP/1.1("HOL Blocking発生")
        A -- |Req1| --> B;
        A -- |Req2("待機")| --> B;
        B -- |Resp1| --> A;
        B -- |Resp2| --> A;
    end
    subgraph HTTP/2("ストリーム多重化")
        C["HTTP/2 クライアント"] -- |Stream1 Req| --> D("HTTP/2 サーバー");
        C -- |Stream2 Req| --> D;
        D -- |Stream1 Resp| --> C;
        D -- |Stream2 Resp| --> C;
    end
    subgraph HTTP/3("QUIC上で多重化")
        E["HTTP/3 クライアント"] -- |QUIC Stream1 Req| --> F("HTTP/3 サーバー");
        E -- |QUIC Stream2 Req| --> F;
        F -- |QUIC Stream1 Resp| --> E;
        F -- |QUIC Stream2 Resp| --> E;
    end

セキュリティ考慮

HTTP/2は、その設計においてセキュリティを考慮していますが、いくつかの脆弱性や攻撃のリスクも存在します。

  • ダウングレード攻撃: クライアントとサーバー間でHTTP/2への合意がなされず、HTTP/1.1にダウングレードされる攻撃。HTTP/2の多くの実装は、TLS上で動作することを前提としており、TLSのApplication-Layer Protocol Negotiation (ALPN) を使用してプロトコルネゴシエーションを行います。ALPNを使用することで、中間者攻撃によるプロトコルのダウングレードを防止できます。

  • リソース枯渇攻撃 (DoS/DDoS):

    • SETTINGSフレームの悪用: 大量のSETTINGSフレームを送信したり、過度に大きなウィンドウサイズを要求したりすることで、サーバーのリソースを枯渇させる可能性があります。

    • 多数のストリームの確立: 許可された最大ストリーム数を超えて、多くのストリームを同時に開こうとすることで、サーバーのメモリやCPUを消費させ、DoS攻撃につながる可能性があります。

    • PINGフレームの悪用: 大量のPINGフレームを送信し、応答を強制することで、サーバーやネットワークのリソースを消費させる可能性があります。

  • 優先度設定の悪用: クライアントが悪意を持って特定のストリームの優先度を不当に高く設定することで、他の正当なリクエストの処理を遅延させ、サービス品質を低下させる可能性があります。

  • HPACKの脆弱性: ヘッダ圧縮メカニズムであるHPACKは、圧縮後のヘッダ情報を元に元のヘッダ情報を推測するサイドチャネル攻撃(CRIME/BREACH攻撃など)のリスクがあります。これらの攻撃は、HTTPSで暗号化されたCookieなどの機密情報を推測する可能性があります。対策として、機密情報を含むヘッダを圧縮しない、各リクエストごとにCSRFトークンを変更するといった方法があります。

  • 0-RTTの再送リスク: HTTP/2自体に0-RTT接続確立のメカニズムはありませんが、基盤となるTLS 1.3が0-RTTをサポートしています。0-RTTでは、過去の接続情報を使って暗号鍵を再利用するため、リプレイ攻撃のリスクがあります。これは、再送可能なリクエスト(例: GET)に限って利用を推奨し、副作用のあるリクエスト(例: POST)には使用しない、あるいはサーバ側でリプレイ検出メカニズムを実装する必要があります。

実装メモ

HTTP/2の実装において、ネットワークエンジニアが留意すべき点は多岐にわたります。

  • MTU/Path MTU: HTTP/2フレームはTCPセグメント内で送信されるため、基盤となるネットワークのMaximum Transmission Unit (MTU) やPath MTUに注意を払う必要があります。大きなフレームを送信すると、IPレベルでのフラグメンテーションが発生し、性能低下やパケットロス耐性の低下につながることがあります。効果的なフロー制御と適切なフレームサイズの選択が重要です。

  • HOL Blocking回避: HTTP/2はアプリケーション層のHOL Blockingを解消しますが、TCPレベルのHOL Blockingは依然として存在します。これは、TCPが順序保証を提供するため、パケットロスが発生した場合、そのパケットが再送・再構築されるまで、その後のすべてのデータ(異なるストリームのものであっても)がブロックされるためです。この問題を根本的に解決するのがUDPベースのHTTP/3/QUICですが、HTTP/2ではTCPの輻輳制御を最適化し、RTTを最小限に抑えることで影響を緩和することが求められます。

  • キュー制御と優先度: サーバーとクライアントの両方で、送受信キューの効率的な管理と、ストリームの優先度設定への準拠が不可欠です。サーバーは、クライアントから指定された優先度に基づいて、リソースの処理順序や帯域幅の割り当てを適切に調整する必要があります。不適切な優先度処理は、重要なリソースの遅延や、リソース枯渇攻撃への耐性低下につながります。

  • フロー制御の実装: 接続レベルとストリームレベルのフロー制御ウィンドウを適切に管理し、相手側のバッファサイズを超えないようにデータを送信することが重要です。これにより、バッファオーバーフローや不要なパケットドロップを防ぎ、信頼性の高いデータ転送を保証します。

  • 接続の永続性: HTTP/2は単一のTCP接続を長期間維持することを前提としています。このため、アイドルタイムアウトの適切な設定や、接続の状態管理が重要です。また、ネットワークの中断やNATデバイスのタイムアウトに備え、TCPキープアライブやHTTP/2のPINGフレームを利用して接続を維持する戦略も考慮する必要があります。

まとめ

RFC 7540(後にRFC 9113で更新)によって定義されたHTTP/2は、その中核技術であるストリーム多重化により、HTTP/1.xが抱えていた性能上の多くの課題を解決しました。単一のTCP接続上で複数の論理ストリームを並行処理する能力、効率的なヘッダ圧縮(HPACK)、きめ細やかなフロー制御と優先度設定は、Webアプリケーションの応答性向上と帯域幅効率化に大きく貢献しました。

一方で、HTTP/2はTCPレベルのHOL Blockingという根本的な問題を抱えており、この課題はUDPをベースとするQUICとHTTP/3によって完全に解決されることになります。しかし、HTTP/2は依然として広く利用されており、Webパフォーマンス最適化において重要な役割を果たしています。実装においては、セキュリティ上の考慮事項や、MTU、キュー制御、優先度といった細かなネットワーク制御の側面を理解し、適切に対応することが求められます。HTTP/2の理解は、今後のHTTP/3や次世代プロトコルの進化を理解する上でも不可欠な基盤となります。

参考文献: [1] IETF. “RFC 7540 – Hypertext Transfer Protocol Version 2”. May 2015. (Obsoleted by RFC 9113 in June 2022) https://www.rfc-editor.org/rfc/rfc7540 (閲覧日: {{jst_today}}) [2] IETF. “RFC 9113 – HTTP/2”. June 2022. https://www.rfc-editor.org/rfc/rfc9113 (閲覧日: {{jst_today}}) [3] IETF. “RFC 9114 – HTTP/3”. June 2022. https://www.rfc-editor.org/rfc/rfc9114 (閲覧日: {{jst_today}}) [4] IETF. “RFC 9000 – QUIC: A UDP-Based Multiplexed and Secure Transport”. May 2021. https://www.rfc-editor.org/rfc/rfc9000 (閲覧日: {{jst_today}})

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました