Dev Study
← アプリケーションの土台
レッスン 5 / 11·約 9 分·第7期 アプリケーションの土台

なぜ Content-Type と文字コードが要るのか

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理今ここ
  7. DB保存

導線では「型のラベルが無いと受け取る側で何が起きるか」を見ました。今回は同じ話を、ラベルを付ける側の責任として見ます。

まず結論

受け取る側は、届いたバイト列から「これは何か」を読み取れません。本文に書かれていないからです。送る側がラベルを付けない限り、型の決定は受け取る側の推測に移ります。

文字コードも同じ構図です。HTML をどの符号化で復号するかは BOM・Content-Type の charset・`<meta charset>` …と決まった順で探され、どれも無ければ実装依存の既定に落ちます。ラベルを付けるとは、この探索を手前の段で止めることです。

? 読む前に予測

`Content-Type: text/html` だけ(charset 無し)で日本語の HTML を返しました。この文書をどの文字コードで読むかは何が決めますか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

バイト列は、自分が何であるかを名乗らない

前のレッスンで、テンプレートも JSX も最後は文字列を作っているだけだと見ました。その文字列はソケットに書き出される時点でバイト列になります。

届いたバイト列のどこにも「これは HTML です」とは書かれていません。 3c 68 31 3e という4バイトは、<h1> としても、ただのテキストとしても読めます。バイト列は自分の型を名乗りません。

だから HTTP は、型を告げる場所を本文の外に用意しました。Content-Type ヘッダです。RFC 9110 §8.3 は責任の所在を2つの規定語で書き分けます。

送る側は SHOULD、受け取る側は MAY の二択。付けないことは違反ではありませんが、付けなかった時点で型を決める主体が受け取る側に移ります。導線『サーバは何を返すか — Content-Type が無いと何が起きるか』では移った先を受け取る側から見ました。今回は移す側の話です。

同じ §8.3 は、中身を調べる経路についてこうも書きます。

最後の一文が効きます。同じデータ形式を共有する別々の媒体型は、データを見ただけでは区別できない。型とは「このバイト列をどう処理するつもりか」であり、バイト列の性質ではないからです。

具体実物

charset を付けても、本文のバイト列は1ビットも変わらない

文字コードのラベルも同じ場所に載ります。text/html; charset=utf-8 の ; charset=utf-8 がそれです。ここに「charset を指定するとその符号化に変換される」という思い違いがあります。

Node の http で、/nocharset には text/html、/charset には text/html; charset=utf-8 を付け、同じ文字列 <h1>こんにちは</h1> を返すサーバを立てました。

$ curl -si http://127.0.0.1:3901/nocharset(macOS、Node v24.21.0)コマンド出力
HTTP/1.1 200 OK
Content-Type: text/html
Date: Wed, 16 Sep 2026 04:11:47 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 24
 
<h1>こんにちは</h1>
$ curl -si http://127.0.0.1:3901/charset(同環境)コマンド出力
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Date: Wed, 16 Sep 2026 04:11:47 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 24
 
<h1>こんにちは</h1>

本文を生で突き合わせます。

$ curl -s .../nocharset | xxd と shasum -a 256(同環境)コマンド出力
00000000: 3c68 313e e381 93e3 8293 e381 abe3 81a1 <h1>............
00000010: e381 af3c 2f68 313e ...</h1>
27b0bdcb63c7c30400e1e260d30128eff9b05eb87617c73a7fb141661d11e2f5 -
 
$ curl -s .../charset | shasum -a 256
27b0bdcb63c7c30400e1e260d30128eff9b05eb87617c73a7fb141661d11e2f5 -

SHA-256 まで一致しています。 どちらも本文 24 バイト(こんにちは の5文字が UTF-8 で 15 バイト、タグが 9 バイト)。違うのはヘッダに ; charset=utf-8 の分が増えたことだけ。

つまり charset は、本文を変換する指示ではなく「すでにこの符号化で書いてある」というラベルです。第2週『2進数 — オンとオフの並びで、なぜ数が表せるのか』で見たとおり、ビット列自体に「文字」という情報はありません。文字コードは解釈のルールであってデータの性質ではなく、ラベルはそのルールを名指しします。

✓ ここまでの確認

RFC 9110 §8.3 は、Content-Type が無いメッセージの受け取り側について何と規定していますか。

抽象原理

ラベルが無いと、探索が始まる

では /nocharset の側はどの符号化で読まれるのか。「不明だからエラー」にはなりません。探索が始まります。 HTML Standard §13.2.3.2 は、この探索を encoding sniffing algorithm という名前で定義しています。

返すのは符号化と confidence の組で、値は tentative か certain。この戻り値があること自体が、手がかりによって確からしさが違うことを認めた設計です。

encoding sniffing algorithm の探索順(原文の段の番号)
1. BOM sniffing先頭バイトが BOM ならその符号化○ confidence certain
2. ユーザーによる上書き利用者が符号化を明示的に指示○ certain で返してよい
4. transport layer の指定Content-Type の charset(対応する符号化なら)○ confidence certain
5. prescan先頭バイトを走査し `<meta charset>` 等を探す(1024 バイトまで推奨)○ 見つかれば tentative
6〜8. 親文書 / 履歴 / 自動判別親文書の符号化、前回訪問時の情報、頻度分析○ tentative
9. 既定どれも当たらない場合× implementation-defined または user-specified な既定
↓
Content-Type の charset は第4段。ここで止めれば以降の推定に落ちない
  • ・第3段は「もっとバイトが届くまで待ってよい」段で、符号化を返さない
  • ・最終段の既定を仕様は決めていない。推奨表では `ja` が Shift_JIS、`All other locales` が windows-1252
  • ・管理された環境には comprehensive UTF-8 encoding is suggested とある(既定の規定ではない)

実機の /nocharset は BOM も <meta charset> も持ちません。仕様の上では第4段と第5段を素通りして後段へ落ちる、とまでは言えます。**そこから先で画面に何が出るかは扱いません。**実際のブラウザで確かめていないからです。言えるのは「決定が推定の側に移った」という構造までです。

具体実物

JSON は、ラベルではなく規格で固定した

同じ目的を別の手段で達成している例があります。JSON です。RFC 8259 §8.1 は符号化を規格の側で固定しました。

**MUST が付いているのは「closed ecosystem の一部ではないシステム間」**です。「JSON は常に UTF-8」と縮めると、この限定が落ちます。そして媒体型の登録側には、charset を送る余地そのものがありません。

application/json; charset=utf-8 と書いても、準拠する受信側には効果がありません。効果が無いまでが仕様の記述で、有害だとは書かれていない点に注意してください。

BOM の扱いも対照的です。HTML では第1段の最優先の手がかりですが、JSON では転送時に付けることが MUST NOT。

✓ ここまでの確認

RFC 8259 §11 は `application/json` の charset パラメータについて何と書いていますか。

⇄ 2つを見比べる
具体例 A

HTML — メッセージごとにラベルを載せる

charset の有無だけが違う2つの応答です。本文は SHA-256 まで一致し、変わるのはヘッダだけ。にもかかわらず符号化がどの段で決まるかは変わります(charset があれば第4段で certain、無ければ後段の探索へ)。

告げ方の違い(実機の応答と、仕様上の帰結)表
告げ方 本文 符号化を決めるのは
text/html 24B / 27b0bdcb… 探索(BOM→prescan→…→既定)
text/html; charset=utf-8 24B / 27b0bdcb… transport layer(第4段・certain)
具体例 B

JSON — 規格が符号化を1つに固定した

RFC 8259 は §8.1 で符号化を UTF-8 に固定し(closed ecosystem 外の交換について MUST)、§11 の媒体型登録では charset パラメータを定義しません。告げる場所が無い代わりに、告げ忘れも存在しません。

RFC 8259 が符号化について定めていること表
項目 規定 出どころ
符号化 UTF-8(closed ecosystem 外) §8.1 MUST
BOM の付与 付けない(転送時) §8.1 MUST NOT
charset 定義されていない / 効果が無い §11 の Note

HTML は送り手がラベルを付け、JSON は付けません。どちらも「解釈を推測にしない」ためなのに、手段がこれほど違うのはなぜでしょうか。

それぞれの形式が、いつ生まれ、どれだけ多様な符号化の文書を抱えているかを考えてください。

この章のまとめ

  • バイト列は自分の型を名乗らない。RFC 9110 §8.3 は送る側に SHOULD、受け取る側に MAY の二択を与える。付けないことは違反ではないが、型を決める主体が受け取る側に移る。
  • §8.3 は、データ形式を共有する別々の媒体型は impossible to distinguish by inspecting the data alone とも述べる。型は処理の意図であってバイト列の性質ではない。
  • charset は本文を変換する指示ではない。実機の2応答は本文が SHA-256 まで一致した。
  • HTML の符号化は encoding sniffing algorithm で決まる。BOM → ユーザー上書き → transport layer(charset)→ prescan(<meta charset>)→ 親文書 → 履歴 → 自動判別 → 実装依存の既定の順。
  • 「charset 省略時の既定は UTF-8」とは規定されていない。 RFC 9110 は既定を定めず、HTML Standard の最終段は implementation-defined。推奨表の ja は Shift_JIS。
  • JSON は別の手段を取った。§8.1 が符号化を UTF-8 に固定し(closed ecosystem 外という限定付き)、§11 は charset を定義していない。

この先の学びにどう繋がるか

次のレッスンでは、リクエストをまたいで状態が残らない HTTP の性質と、Cookie がその上で何をしているかを見ます。Cookie もまた、本文の外側に載せるラベルです。

媒体型とヘッダの語彙は本コースでは扱いません。既存の「HTTP(RFC 9110)」の HTTPセマンティクス V(表現とコンテントネゴシエーション)、「HTTP/1.1用語(単語帳)」の HTTP/1.1用語 I(メッセージ構文) に譲ります。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 4正解: 0

同じ日本語の HTML を `text/html` と `text/html; charset=utf-8` の2通りで返すと、本文のバイト列はどうなりますか。

用語集(4語)
charset パラメータ
Content-Type に付けて文字符号化方式を示すパラメータ。RFC 9110 §8.3.2 によれば charset 名の照合は case-insensitive。本文を変換する指示ではなくラベル。
encoding sniffing algorithm
HTML Standard §13.2.3.2 が定める、文書を復号する文字符号化を決める手順。符号化と、tentative か certain の confidence を返す。
MIME sniffing
中身のバイト列から媒体型を推定すること。RFC 9110 §8.3 は MAY の一方として認めつつ、誤った結論を導く risk があるとする。
BOM(byte order mark)
U+FEFF。HTML の符号化決定では第1段の最優先の手がかり。一方 RFC 8259 §8.1 は、転送する JSON text の先頭に付けてはならないとする。