- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理今ここ
- 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> を返すサーバを立てました。
HTTP/1.1 200 OKContent-Type: text/htmlDate: Wed, 16 Sep 2026 04:11:47 GMTConnection: keep-aliveKeep-Alive: timeout=5Content-Length: 24<h1>こんにちは</h1>
HTTP/1.1 200 OKContent-Type: text/html; charset=utf-8Date: Wed, 16 Sep 2026 04:11:47 GMTConnection: keep-aliveKeep-Alive: timeout=5Content-Length: 24<h1>こんにちは</h1>
本文を生で突き合わせます。
00000000: 3c68 313e e381 93e3 8293 e381 abe3 81a1 <h1>............00000010: e381 af3c 2f68 313e ...</h1>27b0bdcb63c7c30400e1e260d30128eff9b05eb87617c73a7fb141661d11e2f5 -$ curl -s .../charset | shasum -a 25627b0bdcb63c7c30400e1e260d30128eff9b05eb87617c73a7fb141661d11e2f5 -
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。この戻り値があること自体が、手がかりによって確からしさが違うことを認めた設計です。
- ・第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 パラメータについて何と書いていますか。
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)
JSON — 規格が符号化を1つに固定した
RFC 8259 は §8.1 で符号化を UTF-8 に固定し(closed ecosystem 外の交換について MUST)、§11 の媒体型登録では charset パラメータを定義しません。告げる場所が無い代わりに、告げ忘れも存在しません。
項目 規定 出どころ符号化 UTF-8(closed ecosystem 外) §8.1 MUSTBOM の付与 付けない(転送時) §8.1 MUST NOTcharset 定義されていない / 効果が無い §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(メッセージ構文) に譲ります。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
同じ日本語の 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 の先頭に付けてはならないとする。