ネットワークの「終わりの見えない旅」を支える技術:Transfer-Encoding: chunked の深淵
Web APIの開発やインフラ運用をしていると、必ず一度は壁にぶつかるのが「動的コンテンツのサイズ問題」です。サーバーサイドで生成されるデータ、例えば巨大なデータベースの検索結果や、リアルタイムのログストリーム。これらは生成が完了するまで全体サイズが確定しません。
HTTP/1.0の時代、サーバーはコンテンツの長さを表す `Content-Length` をヘッダーに含める必要がありました。つまり、「すべて生成し終えて、サイズを測ってからでないと送信を開始できない」という制約があったのです。これではリアルタイム性は絶望的ですよね。
そこで登場したのが、HTTP/1.1の革命的機能 `Transfer-Encoding: chunked` です。今日は、この「分割送信」の仕組みを、現場のエンジニア視点で紐解いていきましょう。
—
1. なぜ「Chunked」が必要なのか?
もし `Content-Length` を使おうとすると、サーバーはメモリ上に全データを展開してサイズを計算する必要があります。数GBのデータをメモリに載せれば、当然ながらOOM(Out of Memory)エラーの引き金になりますし、クライアントは最初の1バイトを受け取るまでに膨大な待ち時間を強いられます。
`Transfer-Encoding: chunked` は、このジレンマを解決します。データを小さな「チャンク(塊)」に分割し、「今の塊はこれだけのサイズだよ」という情報を添えて次々に流し込む。これなら、サーバーはデータを生成しつつ即座に送信を開始でき、クライアントは届いた端から表示を開始できるのです。
—
2. チャンクの通信シーケンス
通信の実体は、16進数で記述されたサイズ情報と、データ本体の繰り返しです。
[チャンクサイズ(16進数)]\r\n
[データ本体]\r\n
[チャンクサイズ(16進数)]\r\n
[データ本体]\r\n
…
0\r\n
\r\n
最後に「サイズ0」のチャンクが送られることで、クライアントは「これにてデータ終了(EndOfStream)」と判断します。この `0\r\n\r\n` が、通信の「終わりの合図」というわけです。
—
3. 実践:curlで覗くパケットの裏側
まずは手元の端末で、実際の通信を確認してみましょう。`curl -v` を使えば、生のヘッダーとチャンクの様子が丸見えです。
チャンク転送を行うサーバーにリクエストを投げる
curl -v http://httpbin.org/stream/3
ここで注目すべきはレスポンスヘッダーです。
Transfer-Encoding: chunked
そしてレスポンスボディには、`7d`(16進数)といったサイズを示す文字列が混入しているのが確認できるはずです。これがブラウザやライブラリの内部で自動的にデコードされ、我々が目にするプレーンなデータに復元されています。
—
4. Pythonで実装する「擬似チャンクストリーム」
実際にサーバーを立てる際、どのようにチャンクを制御するか。PythonのFlaskで動的にデータを流す例を紹介します。
from flask import Flask, Response
import time
app = Flask(__name__)
@app.route(‘/stream’)
def stream():
def generate():
# 1秒おきにチャンクを生成して送信
for i in range(5):
yield f”Chunk number {i}\n”
time.sleep(1)
# generatorを渡すとFlaskは自動的にTransfer-Encoding: chunkedを適用する
return Response(generate(), mimetype=’text/plain’)
if __name__ == ‘__main__’:
app.run()
このコードでは、サーバーはレスポンスの総サイズを事前に知る必要はありません。`yield` で生成された瞬間に、ネットワークへパケットが飛んでいきます。これがストリーミング配信の基本形です。
—
5. 現場のトラブルシューティング:ここを見ろ!
実務で「データが途中で切れる」「ブラウザで表示が止まる」というトラブルに遭遇したら、以下のポイントをチェックしてください。
1. プロキシと中継サーバーの介在:
Nginxやロードバランサーがレスポンスをバッファリングしていないか確認してください。`proxy_buffering off;` を設定しないと、バックエンドがチャンクで送っても、中間サーバーがすべて溜め込んでから一括送信し、結果的にリアルタイム性が死ぬことがあります。
2. `0\r\n\r\n` の欠落:
自前でストリーム処理を実装している場合、最後の終端チャンクを送り忘れると、クライアント側は「まだデータが来るはずだ」と待ち続け、タイムアウトを引き起こします。
3. HTTP/1.1の維持:
`Connection: keep-alive` が切断されると、チャンク転送が正常に完遂できないケースがあります。パケットキャプチャ(Wireshark)を撮り、FINパケットがいつ飛んでいるかを追うのが解決の近道です。
—
まとめ
`Transfer-Encoding: chunked` は、HTTP/1.1における「終わりの見えないデータ」を扱うための非常に洗練されたプロトコルです。単なる仕様の暗記ではなく、「なぜサイズを事前に決められないのか?」「どうやって終わりの合図を送るのか?」という設計思想を理解すれば、APIのパフォーマンスチューニングや、複雑なストリーミングアーキテクチャの設計が格段に面白くなるはずです。
ネットワークは生き物です。コマンドを打つだけではなく、パケットがどう旅をしているのか、その呼吸を感じられるようになれば、あなたも立派なインフラエンジニアです。それでは、また現場でお会いしましょう。
コメント