合同会社小村ソフト
第 4 章

HTTP ヘッダとコンテンツ — 何を・どんな形式で

ヘッダは往復書簡の添え書き。宛名の Host、形式と文字コードの Content-Type、量の Content-Length、希望を伝える Accept 系、そしてステートレスの上にセッションを作る Cookie と Set-Cookie を読む。

本文は合っているのに、なぜか化ける・違うものが返る

HTML は正しく書いたはずなのに日本語が「譁�蟄�」のように化ける。API に JSON を送ったのに「形式が不正」と拒否される。同じ URL なのに人によって違う言語のページが出る — こうした「本文は合っているのに」系のトラブルの答えは、たいてい本文ではなくヘッダにあります。

ヘッダは、往復書簡に添えられる 名前: 値 形式の添え書きです。手紙にたとえるなら、宛名・「ワレモノ注意」のラベル・重さの表記・返信用の名札 — 本文そのものではないけれど、本文をどう扱うべきかを決める情報がここに集まっています。3 点セットの 3 つ目「条件と補足」です。

ヘッダは数百種類ありますが、暗記は不要です。この章では「どこ宛か・何がどれだけ・どんな希望で・誰からか」に答える代表 6 つだけを読みます。

HTTP の往復書簡に、宛名の Host、形式の Content-Type、量の Content-Length、希望の Accept、名札の Cookie という添え書きが付いている様子を示す図

ヘッダは本文に添えられたラベルです。宛名(Host)・中身の表示(Content-Type / Content-Length)・希望(Accept 系)・名札(Cookie / Set-Cookie)— 役割で束ねると、数百種類あるヘッダも怖くなくなります。

Host — 1 つの住所に、複数の表札

リクエストの必須ヘッダ Host: example.com は、手紙の宛名です。「IP アドレスに接続しているのだから宛先は自明では?」と思うかもしれませんが、実際には 1 台のサーバ(1 つの IP アドレス)で www.example.comshop.example.com のように複数のサイトを同居させる運用が普通です。

TCP の接続は建物の玄関までしか案内してくれません。どの部屋(サイト)宛かは、手紙の宛名 — Host ヘッダ — で伝えます。サーバはこれを見て返す内容を切り替えます。これが名前ベースのバーチャルホストで、Host を欠いたリクエストが HTTP/1.1 でエラーになるのは、宛名のない手紙は配れないからです。

Content-Type と Content-Length — 中身は何で、どれだけか

レスポンスの Content-Type: text/html; charset=UTF-8 は「中身は HTML で、文字コードは UTF-8 です」というラベルです。前半の text/html(MIME タイプ)でブラウザは「描画する HTML なのか、表示する画像なのか、ダウンロードさせるファイルなのか」を決め、後半の charsetバイト列をどの文字コードで文字に戻すかを決めます。

ここが文字化けの入口です。実体は UTF-8 で保存されているのに charset=Shift_JIS と宣言すれば、ブラウザは律儀に間違ったルールでバイト列を読み、日本語は化けます。エラーにならず化けたまま表示されるのが厄介なところで、対処は「ラベルと中身を一致させる」の一点です。なお Content-Type はレスポンス専用ではありません — POST で JSON を送るときは、リクエスト側に Content-Type: application/json を付けて中身の形式を伝えます。

Content-Length: 1256 は「ボディは 1256 バイトです」という量の表記です。1 つの TCP 接続を使い回して何通も手紙をやり取りする HTTP/1.1 では、受信側はどこまでが今回のボディかを知る必要があり、この数がその区切りになります。

小問 4-1 — 宛名と中身の添え書き

Host と Content-Type、Content-Length — 「どこ宛か・何が・どれだけ」を読みます。

Q1. 1 台のサーバ(1 つの IP アドレス)で www.example.comshop.example.com の 2 つのサイトを運用しています。サーバがどちらのサイト宛のリクエストかを判別する手がかりになるヘッダはどれですか。

Q2. UTF-8 で保存された日本語の HTML を返すレスポンスに Content-Type: text/html; charset=Shift_JIS というヘッダが付いていました。何が起きますか。

Q3. レスポンスヘッダ Content-Length: 1256 の意味として正しいものはどれですか。

Accept 系 — 希望を伝えて、選んでもらう

同じ URL でも、欲しい形は相手によって違います。ブラウザは HTML が欲しいし、プログラムは JSON が欲しい。日本語話者には日本語のページを返したい。この「希望」を伝えるのが Accept 系のリクエストヘッダです。

Accept: application/json
「できれば JSON でください」— 形式の希望
Accept-Language: ja
「できれば日本語でください」— 言語の希望
Accept-Encoding: gzip
「圧縮して送ってくれて構いません」— 転送方法の希望

サーバは提供できる表現の中から選んで返します。これがコンテンツネゴシエーション(内容の交渉)です。ポイントは強制ではなく交渉だということ — 希望が通らないこともあり、実際に何が返ったかはレスポンス側の Content-Type などで確認します。「同じ URL なのに人によって見た目が違う」の種明かしの 1 つがここにあります。

Cookie と Set-Cookie — ステートレスの上にセッションを作る

第 1 章の宿題を回収します。HTTP はステートレスで、サーバは「さっきの続き」を覚えていない。ではログイン状態はどう実現しているのか — 答えは 2 つのヘッダの連携です。

Step 1 — サーバが名札を発行する
ログイン成功のレスポンスに Set-Cookie: session=abc123 を添える。「以後はこの名札を見せてください」
Step 2 — ブラウザが名札を保管する
ブラウザは Cookie をそのサイト用に記憶する
Step 3 — 以後のリクエストに毎回添える
同じサイトへのリクエストには自動で Cookie: session=abc123 が付く。サーバは名札を見て「さっきの人だ」と分かる

つまりセッションとは、ステートレスな往復書簡の 1 通 1 通に、同じ名札を添え続けることで作られる「続きもの」です。サーバが覚えているのは「名札 abc123 は誰か」という対応表だけで、HTTP 自体は最後までステートレスなまま — この構図が読めると、「Cookie を消したらログアウトされた」「別のブラウザではログインしていない扱いになる」といった日常の現象がすべて説明できます。

小問 4-2 — 希望を伝える・名札を付ける

Accept 系のコンテンツネゴシエーションと、Cookie によるセッションの仕組みです。

Q4. リクエストに Accept: application/jsonAccept-Language: ja を付ける「コンテンツネゴシエーション」の説明として正しいものはどれですか。

Q5. ログインの仕組みとして正しい説明はどれですか。

Q6. Content-Type ヘッダについて正しい説明はどれですか。

この章で持ち帰ること

  • ヘッダは往復書簡の添え書き。本文をどう扱うかを決める情報が集まる
  • Host は宛名 — 1 つの IP に複数サイトが同居する名前ベースのバーチャルホストを支える必須ヘッダ
  • Content-Type(+ charset)はラベル。ラベルと中身の不一致が文字化けの入口Content-Length はボディのバイト数で、メッセージの区切りを教える
  • Accept 系は希望の表明(交渉であって強制ではない)。Set-Cookie → Cookie の名札の往復が、ステートレスの上にセッションを作る

次章はヘッダの応用編にして、Web の速さの心臓部 — キャッシュです。「同じものを二度運ばない」ための条件と補足を、手計算とシミュレータで確かめます。