- ブラウザURL入力今ここ
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口今ここ
- アプリ処理
- DB保存
ブラウザとサーバの間を流れる HTTP を、意味と運び方に分けて見ます。両端が何を先に共有しているかが、線上の姿を決めています。
まず結論
GET の意味は HTTP/1.1 でも 2 でも 3 でも同じで、RFC 9110 がまとめて定義しています。版ごとに違うのは、その意味を線に載せる書き方です。
HTTP/2 で送った要求を線上で見ると、GET は1バイト 0x82 になり、版番号はどこにもありません。両端が同じ表と同じ前提を先に持っているからです。このコースは「共有していれば省略でき、共有していなければ往復が要る」という見方で、HTTP と TLS の仕組みを読んでいきます。
curl 8.7.1 で HTTP/2(平文の h2c)の要求を送ると、`curl -v` の出力に `> GET /slow HTTP/2` という行が出ました。この文字列は線上をこの形で流れたでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
意味は1か所で定義し、書き方は版に任せる
HTTP には 1.1・2・3 の3つの主要な版があります。新しい版が古い版を置き換えたと考えがちですが、RFC 9110 §1.2 はそう書いていません。
All three major versions of HTTP rely on the semantics defined by this document. They have not obsoleted each other because each one has specific benefits and limitations depending on the context of use.
GET や 200 が何を意味するかは、この1か所で決まっています。では版ごとに何が違うのか。§6 は Each major version of HTTP defines its own syntax for communicating messages. と書き、その狙いを a message in one version can be relayed through other versions without changing its meaning と述べます。意味は共通で、線に載せる書き方が版ごとに違います。
違いの中身も書かれています。メッセージの区切り方について §6.1 は Each major version of HTTP defines its own framing mechanism.。メソッドや対象を運ぶ部分(制御データ)について §6.2 は次のように書きます。
In HTTP/1.1 ([HTTP/1.1]) and earlier, control data is sent as the first line of a message. In HTTP/2 ([HTTP/2]) and HTTP/3 ([HTTP/3]), control data is sent as pseudo-header fields with a reserved name prefix (e.g., ":authority").
- ・HTTP/1.1 の先頭行は RFC 9112 §3 の request-line = method SP request-target SP HTTP-version
- ・HTTP/2 の線上の姿は下の実測で見る
HTTP/2 の要求を、表示と線上の両方で見る
Node の node:http2 で平文の HTTP/2 サーバ(h2c)を立て、受け取ったヘッダをそのまま記録させました。そこへ macOS 標準の curl 8.7.1(nghttp2 1.68.1)で要求を送ります。
* Connected to 127.0.0.1 (127.0.0.1) port 18081* [HTTP/2] [1] OPENED stream for http://127.0.0.1:18081/slow* [HTTP/2] [1] [:method: GET]* [HTTP/2] [1] [:scheme: http]* [HTTP/2] [1] [:authority: 127.0.0.1:18081]* [HTTP/2] [1] [:path: /slow]* [HTTP/2] [1] [user-agent: curl/8.7.1]* [HTTP/2] [1] [accept: */*]> GET /slow HTTP/2> Host: 127.0.0.1:18081> User-Agent: curl/8.7.1> Accept: */*>* Request completely sent off< HTTP/2 200
同じ要求が2回書かれています。* [HTTP/2] の行は擬似ヘッダ、> の行は見慣れた HTTP/1.1 の形です。サーバ側の記録はこうでした。
h2c listening v24.21.0received headers: {":method":"GET",":scheme":"http",":authority":"127.0.0.1:18081",":path":"/slow","user-agent":"curl/8.7.1","accept":"*/*"}
host のキーがありません。 宛先は :authority で届いています。> Host: の行は curl が組み直した表示でした。
では線上には何があったのか。応答しない生の TCP の受け口に同じ要求を送らせ、届いたバイトをそのまま出しました。
total bytes: 109505249202a20485454502f322e300d0a0d0a534d0d0a0d0a000012040000000000000300000064000400a000000002000000000000040800000000003e7f00010000240105000000018286418b089d5c0b8170dc0bc07c3f048461141fc77a8825b650c3cbbab87f53032a2f2a
109バイトは、接続の最初に1回だけ送る固定の列(プリフェイス)24、SETTINGS 27、WINDOW_UPDATE 13、要求本体の HEADERS 45 です。"GET"・"/slow"・"Host" の文字列は1つも含まれていません。 HEADERS の中身(36バイト)を RFC 7541 の静的表で読むとこうなります。表のハフマン符号は、よく出る文字ほど短いビット列で表す符号で、HPACK では HTTP ヘッダの統計から作られたものを使います(RFC 7541 付録B)。
バイト 意味82 :method: GET(静的表の索引2そのもの)86 :scheme: http(索引6)41 8b + 11バイト :authority(名前は索引1、値はハフマン符号)04 84 + 4バイト :path(名前は索引4、値 /slow は4バイト)7a 88 + 8バイト user-agent(名前は索引58)53 03 2a 2f 2a accept: */*(名前は索引19、値は文字のまま)
GET は 0x82 の1バイトでした。 0x82 は「表の索引2を見よ」という意味で、索引2は :method と GET の組です。
curl 8.7.1 で HTTP/2 の要求を送ったとき、`-v` の出力に出る `> Host: 127.0.0.1:18081` の行について正しいのはどれですか。
省略できるのは、両端が先に持っているものだけ
1バイトで済んだ理由は、表が線の外にあることです。RFC 7541 §2.3.1 は静的表を a predefined static list of header fields と定めています。送り手と受け手が同じ表を最初から持っているので、中身を送らず番号だけで足ります。
版番号も同じ理由で消えています。HTTP/1.1 の先頭行は、RFC 9112 §3 のとおり要求のたびに版を書きます(ends with the protocol version)。HTTP/2 について RFC 9113 §8.3.1 はこう書きます。
Individual HTTP/2 requests do not carry an explicit indicator of protocol version. All HTTP/2 requests implicitly have a protocol version of "2.0"
この接続が HTTP/2 であることは両端がすでに知っています。今回はオプション名のとおり、「相手は HTTP/2 を話す」という事前の知識を curl に与えました。プリフェイスに見える HTTP/2.0 の文字は接続に1回の固定の列で、要求ごとの版表示ではありません。
ここから、このコースの芯が出てきます。通信の仕組みは、両端が何を共有しているかで決まる。共有していれば省略でき、共有していなければ往復が要る。 12本はこの順に進みます。まず版に共通の意味(知らない値・リダイレクト・条件付き要求)、次に TLS、それから 1.1→2→3 の運び方の変化、最後に版をどう選ぶかです。
13 と 1101 — 同じ数、違う書き方
コンピュータの仕組みのコースの「2進数」の回で、2進数は「違うのは、いくつで桁が繰り上がるかだけ」だと見ました。10進数の 13 と2進数の 1101 は同じ数です。
書かれたもの 10進数として読む 2進数として読む13 13 (2進数では書けない)1101 1101 1310 10 2
最後の行が要点です。「10」だけを渡されても、どちらの基数か知らなければ値が決まりません。 書き手と読み手が基数を共有していれば、紙には数字だけを書けば足ります。0x82 が :method: GET と読めるのも、読み手が同じ表を持っているときだけです。
HTTP/2 の要求には版番号が書かれていないのに、受け手が HTTP/2 の要求として扱えるのはなぜですか。
HTTP/1.1 と HTTP/2 の GET
意味は RFC 9110 が1か所で定める同じ GET です。HTTP/1.1 は先頭行に文字で書き、HTTP/2 は静的表の索引2を指す 0x82 の1バイトで送りました。版番号も、接続が HTTP/2 だと両端が知っているので要求には載りません。
版 線上の書き方HTTP/1.1 GET /slow HTTP/1.1(文字の先頭行)HTTP/2 82(索引2)ほか擬似ヘッダ。版番号なし
10進数と2進数の 13
13 と 1101 は同じ数です。どちらで書いても値は変わりませんが、10 という並びは基数を知らなければ10とも2とも読めます。書き手と読み手が基数を共有しているから、数字だけを書けば済みます。
基数 書き方10 132 1101
プロトコルの版と数の基数。まったく別の話に見えますが、共通する構造は何でしょうか。
「短く書けているのは、読み手が何を先に知っているからか」を両方で探してください。
この章のまとめ
- GET の意味は RFC 9110 が 全版に共通して1か所で定める。3つの版は互いを廃止していない(§1.2)。
- 版ごとに違うのは 区切り方(§6.1)と制御データの運び方(§6.2)。1.1 は先頭行、2 と 3 は擬似ヘッダ。
- curl 8.7.1 の HTTP/2 で、
>行は 組み直した表示。線上の109バイトに "GET"・"/slow"・"Host" の文字列は無く、サーバの受信ヘッダにも host は無かった。 - GET が 0x82 の1バイトになるのは、両端が同じ静的表を最初から持っているから。
- HTTP/2 の要求には 版番号が無い(§8.3.1)。接続が HTTP/2 であることを両端が共有しているから。
- コースの芯: 共有していれば省略でき、共有していなければ往復が要る。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
RFC 9110 §1.2 は、HTTP の3つの主要な版の関係をどう書いていますか。
用語集(4語)
- セマンティクス(意味)
- メソッドやステータスコードが何を意味するかの取り決め。RFC 9110 が全版に共通して定義する。書き方の規則である構文と対になる。
- フレーミング
- メッセージがどこで始まりどこで終わるかを区切る仕組み。RFC 9110 §6.1 は版ごとに独自の方式を定めるとする。
- 擬似ヘッダ
- HTTP/2 と HTTP/3 で、メソッドや対象を運ぶための `:` で始まる名前のフィールド(`:method` `:path` `:authority` など)。HTTP/1.1 の先頭行の役を担う。
- 静的表(HPACK)
- RFC 7541 が定める、よく使うヘッダの名前と値に索引を振った変更不能の表。両端が最初から同じものを持つので、索引1つで送れる。