この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。RFC 8297とNode.jsのwriteEarlyHintsの仕様を確認し、localhostで情報応答と最終応答を見分ける実験を組み立てました。対象環境では実行していません。
検証ステータス:📘 RFC・Node.js公式資料確認済み/実機未確認
HTTPのステータスは200や404だけではありません。103 Early Hintsは、サーバーがHTMLなどの最終応答を返す前に、ブラウザーへリソースの読み込み候補を伝える情報応答です。103を見たことだけでは、サイトが正常終了したかどうかは分かりません。
この違いをlocalhostで観察します。Node.js 18.11以上とcurlがあれば、外部のサイトを使わずに試せます。
HTTP 103と200の役割
クライアント サーバー
GET / -------->
<-------- 103 Early Hints
Link: </style.css>; rel=preload
<-------- 200 OK
最終HTML
RFC 8297では103をinformational responseとして定義しています。読み込みの「ヒント」はクライアントによる先行準備の材料です。最終的な200や500の意味を置き換えるものではありません。
| ステータス | 主な役割 | 成功判定に使うか |
|---|---|---|
| 103 Early Hints | Linkなどの事前の手掛かり | 単独では使わない |
| 200 OK | 通常の最終レスポンス | 応答内容と合わせて判断 |
| 404 Not Found | 対象が見つからない | 最終結果として確認 |
| 500 Internal Server Error | サーバー側の異常 | エラーとして扱う |
動かしてみる
early-hints-local.jsというファイルを作ります。
const http = require("node:http");
const server = http.createServer((req, res) => {
if (req.url === "/style.css") {
res.writeHead(200, { "content-type": "text/css" });
res.end("body { color: navy; }");
return;
}
if (req.url !== "/") {
res.writeHead(404);
res.end("not found");
return;
}
// 最終応答の準備が終わる前にLinkヒントを送る
res.writeEarlyHints({
link: "</style.css>; rel=preload; as=style"
});
setTimeout(() => {
res.writeHead(200, {
"content-type": "text/html; charset=utf-8",
link: "</style.css>; rel=preload; as=style"
});
res.end("<!doctype html><h1>hello</h1>");
}, 700);
});
server.listen(30031, "127.0.0.1", () => {
console.log("http://127.0.0.1:30031/");
});
127.0.0.1でのみ待ち受けるため、外部には公開しません。ポート30031がほかのプロセスと衝突した場合は数字を変更してください。
端末Aでサーバーを起動し、端末Bでcurlを使います。
node early-hints-local.js
curl --http1.1 --include --no-progress-meter \ http://127.0.0.1:30031/
期待されるレスポンスの並びです(実機出力ではありません)。
HTTP/1.1 103 Early Hints Link: </style.css>; rel=preload; as=style HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ... <!doctype html><h1>hello</h1>
サーバーを終了するときはCtrl+Cを押します。Node.jsの起動時にEADDRINUSEが出たら同じポートを別のプログラムが使っています。既存のサービスを停止するのではなく、サンプル側のポートを変えてください。
Daily-Code-Samples:HTTP 103のローカル実験に実行版と説明を置きました。
1か所変えて観察する
res.writeEarlyHints(...)の行をコメントアウトし、再起動して同じcurlを実行します。今度は103がなくなり、最終の200だけが残る想定です。
// res.writeEarlyHints({
// link: "</style.css>; rel=preload; as=style"
// });
これで103と200が独立していることを確認します。さらにlocalhostでres.writeHead(500)へ変えれば、103が先に返っていても後でエラーになるケースを再現できます。
Linkヘッダーの意味と限界
</style.css>; rel=preload; as=styleの各部分は、事前読み込み候補のURL、関係(preload)、スタイルシートとしての扱いを示します。しかし、ブラウザーが必ず先読みするとは限りません。curlにヘッダーが出たことも、CSSがダウンロードされた証拠ではありません。
実際にリソースが早く取得されるかは、ブラウザーの開発者ツールのNetwork表示やパフォーマンス測定で検証します。キャッシュが効いている場合も、前提が変わります。
仕事で使う際の注意
プロキシ/CDNや古いHTTP/1.1クライアントは、情報応答を期待どおり扱わないことがあります。RFC 8297には情報応答の相互運用性についてのセキュリティ上の注意点があります。
また、HTTPの監視処理で、最初に見えた103を「応答終了」と扱わないことが重要です。103を受信した、最終ステータスが200だった、先読みが本当に行われたの3つを別々に記録します。
実サイトで使うなら、正しいLink先、実際の最終応答、クライアント互換性、負荷への影響を順番に確認します。導入の前後で実測しない限り、速度改善を断定しないでください。
