終わりの見えないデータに光を:HTTP/1.1 `Transfer-Encoding: chunked` の舞台裏
ネットワークエンジニアとして現場を渡り歩いていると、「なぜかレスポンスが途中で切れる」「動的生成したはずのデータが正しくブラウザに届かない」といった相談をよく受けます。その多くが、HTTP/1.1の根幹を支える `Transfer-Encoding: chunked` の挙動を、概念的にしか理解していないことに起因しています。
今回は、RFC 7230(旧 RFC 2616)で規定されたこの「分割送信」の仕組みを、パケットレベルの挙動からデバッグの実践まで、泥臭く解剖していきましょう。
—
1. なぜ「Content-Length」ではダメなのか?
HTTP/1.0の時代、サーバーはレスポンスを送る前に必ずコンテンツ全体のサイズを計算し、`Content-Length` ヘッダーを付与する必要がありました。しかし、考えてみてください。データベースからストリーミング的にログを吐き出したり、動的に生成される複雑なHTMLを構築したりする場合、「送信を開始するその瞬間に、最終的なバイト数を確定させる」のは至難の業です。
そこで登場したのが `Transfer-Encoding: chunked` です。これは、「全体のサイズはわからないけれど、小分けにした塊(チャンク)を順次送るから、最後に空のチャンクが来たら終わりとみなしてくれ」というプロトコル上の合意です。
—
2. 通信フロー:チャンクの作法
チャンク形式のデータは、以下のような構造でネットワークを流れます。
[チャンクサイズ(16進数)]\r\n
[チャンクデータ]\r\n
[チャンクサイズ(16進数)]\r\n
[チャンクデータ]\r\n
0\r\n
\r\n
1. サイズ指定: 各チャンクの先頭には、そのデータの長さが「16進数」で記述されます。
2. データ本体: 指定されたサイズのデータが続きます。
3. 終端(ターミネーター): サイズ `0` のチャンクが送られた時点で、「これ以降、データはない」とクライアントは判断します。
この仕組みにより、サーバーはメモリ上に全データをバッファリングすることなく、生成したものから順次パイプラインに流し込めるのです。
—
3. 実践:チャンク転送を触ってみる
論よりコード。実際に `curl` を使って、チャンク形式でレスポンスを返してくるサーバーの挙動を覗いてみましょう。
curl で中身を丸裸にする
`curl -v` を実行すると、サーバーがどのようにデータを送り出しているかが手に取るようにわかります。
チャンク転送のヘッダーを確認する
curl -v http://example.com/stream-api
レスポンスヘッダーに `Transfer-Encoding: chunked` が含まれていれば、それはまさにチャンク転送です。
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 {i}\n”
time.sleep(1)
yield “Final Chunk”
# Flaskはジェネレーターを渡すと自動的にchunked転送に切り替えます
return Response(generate(), mimetype=’text/plain’)
if __name__ == ‘__main__’:
app.run()
—
4. 現場で役立つデバッグTips
運用中に「チャンク転送がうまくいかない」というトラブルに出くわしたとき、確認すべきポイントは決まっています。
① プロキシ・ロードバランサーの介入
最も多いのが、途中のNginxやロードバランサーがチャンクをバッファリングし、全データが揃うまでクライアントに転送しないケースです。
Nginx設定のチェックポイント:
プロキシ側の設定でバッファリングを無効化する
proxy_buffering off;
チャンクをそのまま透過させる
chunked_transfer_encoding on;
② 「Content-Length」の二重指定
HTTP/1.1の仕様では、`Transfer-Encoding: chunked` を使用する場合、`Content-Length` を付与してはいけません。もし両方存在すると、クライアントはどちらを信頼すべきか混乱し、接続切断(Connection Reset)を引き起こす可能性があります。
③ 終端の「0」が見えているか
ネットワークキャプチャ(Wireshark等)でパケットを追う際は、最後に `0\r\n\r\n` が到達しているかを確認してください。これが欠落している場合、クライアントは「まだ続きがあるはずだ」と待ち続け、タイムアウトエラーとなります。
—
最後に:プロトコルと向き合うということ
`Transfer-Encoding: chunked` は、Webが「固定されたファイル」を届けるメディアから「生きている情報」を伝えるメディアへと進化した歴史の産物です。
仕様書を追うだけでは見えない「なぜここでバッファリングが発生するのか」「なぜこのヘッダーが競合するのか」という問いは、すべてパケットの流れとOSのバッファ管理の中に答えがあります。次に誰かが「レスポンスが途中で止まる」と泣きついてきたら、まずは `Transfer-Encoding` が意図通りに機能しているか、プロキシ層が余計な親切(バッファリング)をしていないかを確認してみてください。
プロトコルを深く理解することは、サーバーのコードを書くこと以上に、強固なインフラを支える鍵になります。現場からは以上です。
コメント