Windows 95 のパソコンで、今のインターネットを見る。
やってみると、これは「古い機械を頑張らせる」話ではなかった。古い機械には原理的にできないことを、代わりにやってあげる装置を作る話だった。家庭内のAI基盤を載せているサーバに、そのための中継役を一つ立てた。今回はその中身と、作るうえで判断が要った点をまとめる。
壁は七枚あって、五枚は本人にはどうにもできない
1996年のブラウザと2026年のWebの間にあるものを並べると、こうなる。
| 壁 | 中身 | 誰が解けるか |
|---|---|---|
| TLS | 今はTLS1.2以上。古いブラウザはSSL3止まり。証明書も期限切れ | 中継役だけ |
| HTTP/1.1 | chunked転送・keep-alive・Host前提 | 中継役だけ |
| 圧縮 | gzip / brotli | 中継役だけ |
| ウイルス | 古いOSに対策ソフトが無い | 中継役だけ |
| 文字コード | 今はUTF-8。古い日本語ブラウザはShift_JIS | 中継役でやると安い |
| HTML/CSS/JS | divとflexとES6 | 同上 |
| 画像 | WebP / AVIF / SVG | 同上 |
上の四つは、レトロ機の側でどう頑張っても解けない。証明書ストアを更新することも、新しい暗号を実装することもできない。だから中継役を置く、という結論は最初から動かなかった。
逆に言うと、下の三つは「やらなくても動く」。実際、最初に動かした版は素通しだけの実装だった。壊れる場所を先に知りたかったからだ。
工夫①:暗号化されたまま素通しする経路を、あえて塞いだ
古いブラウザにプロキシを設定して https:// のページを開くと、ブラウザは CONNECT という要求を出す。「そこから先は中身を見ずに素通ししてくれ」という意味だ。
これを受けないことにした。素通しした先で古い暗号を使った通信が始まり、必ず失敗する。しかもこちらは中身を見ていないので、何も助けられない。通せるが必ず失敗する経路は、通さないほうがいい。
代わりにこうした。
- レトロ機からは常に
http://で開いてもらう - 中継役は上流へ出るとき、まず
httpsで取りに行く(駄目ならhttpへ落ちる) - 返ってきた応答の中のリンクを
http://へ書き戻す ―― 平文の世界から出さない
ここで一つ、気づいていなければ確実に踏んでいた罠がある。リダイレクトをブラウザに渡してはいけない。
もし Location: https://… を http://… に書き戻してブラウザへ返すと、ブラウザはそれを再要求し、こちらはまた https へ昇格し、相手はまた「httpsへ移動しろ」と返す。無限ループになる。「httpに書き戻す」方針と「リダイレクトを渡す」方針は両立しない。だから中継役が自分で追いかけて、最終ページだけを渡している。遅いレトロ機にとっても、往復がこちら側で完結するほうが速い。
工夫②:Strict-Transport-Security を必ず落とす
これは地味だが、一つ漏れるだけで設計全体が崩れる。
Strict-Transport-Security は「このサイトは今後必ずhttpsで来い」とブラウザに覚えさせるヘッダだ。これを一度でも通すと、ブラウザは以後こちらの意向と無関係にhttpsで出ていく。平文で運ぶという前提が壊れる。
同じ理由で、応答ヘッダは通す前に一通り消毒している。chunked転送の指定、圧縮の指定、長さの指定 ―― 本文を作り変えている以上、上流が付けた情報の多くはもう送るものを指していない。
この考え方で後から一つ見つけたのが ETag だった。ページを変換して19,917バイトで返しているのに、22,174バイトの変換前の文書を指す検証子をそのまま渡していた。同じURLでも渡す相手によって中身が変わるのに、検証子は同一 ―― つまり嘘をついていた。本文を作り変えたら検証子は付けない、に直した。
工夫③:降格の強さを、機械ごとに選ぶ
最初は「古い機械向けの処理」を一組だけ作るつもりだった。これは間違いだった。
手元に繋がった一台目は Windows XP + IE8 だった。この世代はCSSがほぼ通り、JavaScriptも動く。ここにHTML 3.2への降格をかけるのは、改善ではなく改悪だ。一方、二台目の Windows 95 + IE3 には全部要る。
そこで、ブラウザが送ってくる名前(User-Agent)を見て、三段階から選ぶようにした。
| 区分 | 対象 | やること |
|---|---|---|
modern |
IE7以上・現代のブラウザ・不明なもの | 何もしない |
mid |
IE5〜6 | スクリプト除去 |
old |
IE4以下・Netscape | スクリプト除去・CSS除去・文字コード変換・画像変換・本文抽出 |
不明なものを modern に倒しているのが要点だ。「何もしない」は素通しと同じで、退行が起きない。逆に倒すと、正体の分からない相手を勝手に劣化させることになる。古い機械が不明として来ても、その名前は記録に残るので、見てから足せばいい。
工夫④:CSSは「効かない」より「半分だけ効く」ほうが悪い
IE3はCSSを部分的に解釈する。この「部分的に」が厄介だった。
実機では、文字色は拾うのに背景色を拾わず、本文が背景と同化して見えなくなっていた。文字はちゃんと届いている。ただ見えない。
だから old ではCSSを丸ごと落として、素の黒文字・白背景に戻す。ただし bgcolor のようなHTML属性は残す ―― そちらは古いブラウザ本来の表現手段だからだ。
面白いのは、この判断を一度下げていたことだ。文字化けしていた段階では「レイアウトも表組みもフォーム部品も描けているから、降格の優先度は低い」と見ていた。文字が読めるようになって初めて、CSSが本文を消していることが見えた。症状が重なっているときは、上の層を直すまで下の層が見えない。
工夫⑤:文字コードは変換する。ただし、文字コードの定義は返さない
ここが今回いちばん時間を使ったところで、結論だけ書くとこうなる。
UTF-8をcp932(Windows版のShift_JIS)へ変換し、文字コードの定義はどこにも返さない。
定義を返すと、IE3は文書ごと拒否した。Shift_JIS という名前がIANAに登録されたのは1997年で、IE3は1996年のソフトだからだ。当時の呼び名 x-sjis でも駄目だった。日本語版Windows 95の既定はShift_JISなので、黙って渡せば素直に読む。
細かい工夫としては、cp932に無い文字を「近い字」へ寄せている。ダッシュ・波ダッシュ・ノーブレークスペース・中黒などだ。ここを通さずに落とすと、化けは直っても「?」だらけで読めないままになる。実測で、青空文庫のトップページは変換不能文字ゼロになった。
もう一つ。古いブラウザは日本語URLをパーセントエンコードせず、そのページの文字コードの生バイトのまま送ってくる。こちらがcp932で渡している以上、URLもcp932で来る。上流へそのまま渡すと落ちるので、入口で一度だけUTF-8のパーセントエンコードへ直している。検索フォームの送信でも同じ経路を通る。
工夫⑥:SVGは「壊れた画像」にせず、無かったことにする
IE3が確実に描けるのはGIFとJPEGだけだ。PNGは赤い×になる。だから変換する。
- 透明を持つ画像はGIF(JPEGは透明を表現できない)
- 小さい画像もGIF(ロゴやアイコン。JPEGにすると輪郭がにじむ)
- 大きい写真はJPEG(256色のGIFでは汚くなる)
- 長辺が1024pxを超えたら縮める(レトロ機のメモリと描画時間のため)
問題はSVGだった。これはベクタ形式なので、そのまま渡しても描けない。かといって画像変換ライブラリでも読めない。
そこで透明1ドットのGIFに置き換えた。素通しすると「壊れた画像」のアイコンが並んで、実際より壊れて見える。出せないものは、無かったことにするほうが読みやすい。
そして、読めない画像・変換に失敗した画像は素通しする。変換のせいで表示を落とすのが一番まずい。
工夫⑦:本文だけ抜き出す。ただし失敗したら元に戻す
意外だったのは、今のページもサーバがHTMLを組み立てている限り、テキストは生き残ることだった。HTML5の <article> や <section> はIE3にとって未知のタグだが、未知のタグは無視され中身は描画される。
実測すると、日本語版Wikipediaの記事で14,865字が読めていた。問題は量で、その1ページに190KB。Windows 95には重い。
そこで広告・ナビ・サイドバーを落として、本文だけをHTML 3.2相当に組み直す機能を足した。190KB → 49KB になった。
ここで大事なのは判定を外したときの振る舞いだ。痩せた結果しか取れなかったら、元のページをそのまま通す。抽出は当たり外れがある。外したときに読めなくなるのが最悪の結果なので、読めていたものを壊さないを最優先にした。
実際、最初の実装は青空文庫の作品一覧を「本文」と誤認した。表にリンクが並ぶ構造は、文字量だけ見ると本文に見える。リンクが本文より優勢なら、それは索引であって本文ではないという足切りを入れて解決した。索引ページは「本文が無いのが正常」だ。
工夫⑧:AIへの入口は、1996年のやり方が最適解だった
この家のAI基盤に質問できる入口も付けた。推論には数十秒かかる。IE3にはJavaScriptもXHRもない。
使ったのは <meta http-equiv="refresh"> ―― 指定秒後に自分自身を読み直す、あの古い仕組みだ。質問を投げると「考えています…」の画面が出て、5秒ごとにひとりでに更新され、答えが出たら表示に切り替わる。
今ならWebSocketやSSEで書くところを、30年前の道具でそのまま実現できた。相手が持っている機能だけで組むと、こういうことが起きる。
工夫⑨:外に開く穴だけは、実験場でも緩めない
この中継役を置いたサーバは、外向けにWebサイトを公開しているマシンでもある。そこにオープンプロキシを作れば、数時間で踏み台にされる。
この基盤は「個人の実験場であって本番ではない」という方針で作っていて、たいていの作り込みは過剰になりやすい。だがここだけは別だと決めて、三枚重ねた。
- 待ち受けアドレスはLAN側に固定。全アドレスで起動しようとしたら起動そのものを止める
- 接続元のアドレス範囲を限定。指定が空なら誰も通さない(開くほうへ倒れない)
- 中継先が内部アドレスなら拒否。レトロ機から家庭内の他のサーバを叩けないようにする
三番目には後から例外を一つ足した。レトロ機に検索手段を与えるため、LAN内で動かしている検索エンジンだけを名指しで許可した。「内部アドレス全体を開ける」という解き方はしていない。それをすると、この防御はその場で意味を失う。
工夫⑩:全部に消火栓を付け、いま何が効いているかを画面に出す
降格処理はどれも個別に止められる。文字コードの定義に使う名前も変えられる。変換先をJISやEUC-JPに切り替えることもできる。全部まとめて既定に戻す入口もある。
そしていま何が効いているかを、必ず画面に出す。
これは診断のために作った仕組みだが、作ってから決着が速くなった。実機でしか再現しない症状は、実機の側から条件を変えられないと、いつまでも推測が続く。レトロ機のブラウザからリンクを押すだけで条件を切り替えられるようにしたことが、結果的に一番効いた工夫だった。
今できること
Windows 95 + IE3 から、日本語のページが読めて、画像が出て、検索ができて、AIと日本語で会話できる。テストは実ソケットを使うものを含めて300件強。
残っているのは、ダウンロード時のウイルスチェックと、PC-98のようなテキスト端末向けの経路だ。前者は古いOSに対策ソフトが無い以上、中継役がやるしかない仕事なので、いずれ足すことになる。
最後に、この作業でいちばん面白かった点を書いておく。やったことの半分は「新しい機能を足す」ではなく「今の当たり前をやめる」だった。暗号化を素通ししない。文字コードの定義を返さない。CSSを効かせない。出せない画像は無かったことにする ―― 相手が1996年にいるなら、こちらが1996年まで戻るのが正しい。