Chunked TransferはHTTPプロトコルのメカニズムであり、サーバーが応答本文を個別の断片(チャンク)に分けて、データの総サイズを事前に指定せずに送信します。各チャンクには16進数形式のサイズと指定された長さのデータが含まれ、最後にゼロサイズのチャンクで終了します。MDN Web Docs、2025によると、Transfer-Encoding: chunkedは応答サイズが事前に不明な場合にサーバーによって自動的に有効化されます。たとえば、オンザフライでのコンテンツ生成やデータのストリーミング転送時などです。
重要ポイント
Chunked TransferはHTTP/1.1仕様(RFC 7230、セクション4.1)で定義されたHTTPメカニズムであり、サーバーが合計Content-Lengthを指定せずに応答本文を部分的に送信できるようにします。送信前に応答サイズを計算する代わりに、サーバーは準備ができたデータの断片をすぐに送信します。各断片には独自のサイズヘッダーが付属し、クライアントが断片から応答を組み立てられます。
このメカニズムはTransfer-Encoding: chunkedヘッダーによって有効化されます。クライアントが応答でこのヘッダーを確認すると、本文がチャンクで送信されることを認識し、ループで応答を読み取る必要があります。チャンクサイズを読み取り、指定されたサイズのデータを読み取り、繰り返します。プロセスはゼロサイズのチャンクが検出されると終了します。Chunked TransferはHTTP/1.1の必須部分であり、すべての最新のWebサーバーとHTTPクライアントでサポートされています。
チャンク転送を使用する主な理由は動的コンテンツ生成です。サーバーがデータベースクエリ、外部API、または長時間の計算に基づいて応答を生成する場合、結果のサイズを事前に把握できません。応答全体をメモリにバッファリングする代わりに(大量のデータではリスクが高い)、サーバーはTransfer-Encoding: chunkedを有効にして、データが利用可能になり次第送信します。これはメモリが限られているサーバーや、サイズが非常に大きくなる可能性がある応答(100 MB以上)にとって特に重要です。
HTTP/2では、プロトコルがフレームレベルでストリーム多重化を使用するため、チャンク転送メカニズム自体は存在しません。HTTP/2では、任意のサイズのデータがDATAフレームで送信され、応答本文のサイズを事前に宣言する必要はありません。ストリームはいつでも閉じることができます。最新のサーバーは、HTTP/2アップストリームにプロキシする際に、HTTP/1.1のチャンク応答を同等のストリーミング送信に自動的に変換します。Chunked TransferはHTTP/1.1接続に関連し続けています。
サーバーがChunked Transferを使用することを決定すると、Content-Lengthを計算せずにTransfer-Encoding: chunkedヘッダーを送信します。応答本文はチャンクのシーケンスとして形成されます。各チャンクは、16進数形式(0x接頭辞なし)のチャンクサイズを含む行で始まり、その後にCRLF( )が続きます。次に、指定されたサイズのチャンクデータが来て、CRLFで終了します。最後のチャンクのサイズは0で、その後にtrailerヘッダーが続く場合があります。
16進数のサイズにより、1バイトから理論上無制限のサイズまで、任意のサイズのチャンクを送信できます。実際には、チャンクサイズはサーバーによって選択されます。一般的な値は4 KB、8 KB、16 KBです。最適なチャンクサイズは、トランスポート層での断片化を最小限に抑えるために、TCPセグメントサイズ(イーサネットの場合は通常1460バイト)の倍数にする必要があります。Nginxはデフォルトで4 KBのチャンクを、Apacheは8 KBのチャンクを使用します。
Transfer-Encoding: chunkedを受信したクライアントは、終了ゼロチャンクまで応答をチャンクごとに読み取る必要があります。クライアントがチャンク転送をサポートしていない場合、サーバーはこのモードを使用できません。実際には、すべての最新のHTTPクライアント(ブラウザ、OkHttp、URLSession、curl)はチャンク応答を完全にサポートしています。ストリーミング読み取りにより、クライアントは完全な応答を受信する前にデータの処理を開始でき、これはパフォーマンスにとって重要です。
| チャンク要素 | 形式 | 例 |
|---|---|---|
| チャンクサイズ | HEX + CRLF | 1000 |
| チャンクデータ | [サイズバイト] + CRLF | [4096バイトのデータ] |
| 終了チャンク | 0 | 0 |
| Trailer(オプション) | ヘッダー + CRLF | Expires: Wed, 21 Oct 2025 |
Chunked Transferはtrailerヘッダー(最後のチャンクの後に送信される追加のHTTPヘッダー)をサポートしています。これは応答生成の完了後にのみ判明するメタデータに便利です。たとえば、Content-MD5やX-Compression-Ratioなどです。TrailerヘッダーはTrailerヘッダーで宣言する必要があります:Trailer: Content-MD5, X-Compression-Ratio。実際にはtrailerが使用されることは稀で、ほとんどのサーバーは応答にこれらを含めません。
チャンク応答には厳密に定義された構造があり、クライアントは正しく解析する必要があります。Transfer-Encoding: chunkedを使用したHTTP応答の完全な例を見てみましょう。ヘッダーと空行の後、応答本文が始まります。本文の構造は次のシーケンスです:チャンクサイズ データ チャンクサイズ データ ... 0 まで。各サイズはASCII文字を使用した16進数表記で送信されます。
Chunked Transferを使用したサーバー応答の例:
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
7
Hello
6
World!
0
この例では、サーバーは文字列“Hello World!”を2つのチャンクで送信します。最初のチャンクは7バイトで“Hello ”を含み、2番目は6バイトで“World!”を含みます。クライアントは両方のチャンクからデータを収集し、完全な文字列を取得します。重要:チャンクサイズにはデータのみが含まれ、チャンク自体のCRLF区切り文字は含まれません。終了空チャンク(0 )はクライアントに送信が完了したことを通知します。
import java.net.HttpURLConnection
import java.io.BufferedReader
import java.io.InputStreamReader
fun readChunkedResponse() {
val url = java.net.URL("https://stream.example.com/data")
val connection = url.openConnection() as HttpURLConnection
val reader = BufferedReader(
InputStreamReader(connection.inputStream)
)
var line: String?
while (reader.readLine().also { line = it } != null) {
println("チャンク: $line")
}
reader.close()
}
OkHttpはChunked Transferの詳細から開発者を完全に抽象化します。Transfer-Encoding: chunkedを含む応答を受信すると、OkHttpは自動的にチャンクを収集し、response.body?.string()を介して開発者に完全な応答本文を提供します。ストリーミング処理には、response.body?.source()が使用され、BufferedSourceを返し、データが到着するたびに読み取ることができます。開発者は手動で16進サイズやCRLFを解析する必要はなく、ライブラリが自動的に処理します。
Content-LengthとTransfer-Encoding: chunkedは、HTTPメッセージ本文のサイズを指定する2つの相互に排他的な方法です。Content-Lengthは本文の正確なサイズをバイト単位で含むヘッダーです。これはサイズが事前にわかっている応答や本文のあるリクエスト(POST、PUT)に必須です。Content-Lengthにより、クライアントは必要なサイズのバッファを事前に割り当て、すべてのデータが受信されたことを確認できます。
Chunked Transferは、本文のサイズが事前にわからない場合に使用されます。これは主に3つのシナリオで発生します:動的コンテンツ生成(結果がまだ利用できないデータベースクエリなど)、大容量ファイルのストリーミング送信(ファイル全体をメモリにバッファリングしないため)、リアルタイムイベント送信のためのServer-Sent Events(SSE)です。選択はサーバーの責任です。サーバーが送信開始前にサイズを把握している場合は、よりシンプルで予測可能なメカニズムとしてContent-Lengthを使用する必要があります。
HTTP/1.1仕様はContent-LengthとTransfer-Encoding: chunkedの同時使用を禁止しています。サーバーが両方のヘッダーを送信した場合、クライアントはContent-Lengthを無視し、応答をチャンクとして処理する必要があります。優先順位はRFC 7230で確立されており、プロキシサーバーが応答本文を変更し元のContent-Lengthを保持できない場合に対応しています。一部の古いHTTPクライアントはこの状況を誤って処理しますが、最新の実装は仕様に従います。
Content-Lengthを事前に計算できないシナリオがあります。動的レポートはフィルタリングと集計を伴うオンデマンドで生成され、サーバーはデータベースクエリが完了するまでデータ量を把握できません。リアルタイムのカメラからのストリーミングビデオはサイズが無限です。通知のためのSSEやlong pollingは応答が無期限に続く可能性があります。これらすべての場合、Chunked Transferが唯一の正しいメカニズムです。
Chunked Transferはウェブ上の多くのストリーミング技術の基盤となっています。最も有名なのはServer-Sent Events(SSE)で、サーバーがTransfer-Encoding: chunkedを使用して単一のHTTP接続でクライアントにイベントを送信します。SSEは特別なテキスト形式(data: メッセージ )を使用しますが、トランスポート層は通常のチャンク転送です。ブラウザはサーバーが送信するイベントを、応答の完了を待たずに受信します。
オーディオとビデオのストリーミングもChunked Transferに依存しています。Nginx RTMPやWowza Streaming Engineなどのメディアサーバーは、HTTPを介してメディアデータをチャンクで送信します。クライアント側のプレーヤーは最初のチャンクを受信するとすぐに再生を開始し、ファイル全体の読み込みを待ちません。これにより、最初のフレームまでの時間が数十秒から1〜2秒に短縮されます。YouTubeやNetflixはHTTPストリームにまさにこのアプローチを使用しています。
モバイル開発では、Chunked Transferは応答全体をメモリに読み込まずに大量のデータを転送するために使用されます。AndroidでCoilやGlideを介して画像を読み込む場合、ライブラリはチャンクごとにストリーミングデータを読み取り、徐々に画像をデコードします。これにより、OutOfMemoryErrorを発生させずに大きな画像(10 MB以上)を表示できます。OkHttpはresponse.body?.byteStream()を介したストリーミング読み取りをサポートしており、チャンクごとにデータを読み取るInputStreamを返します。
gRPCはHTTP/2を使用しており、ストリーミングはプロトコルレベルで組み込まれているため、個別のチャンクメカニズムは必要ありません。HTTP/1.1で動作するGraphQLサーバーは、サブスクリプション結果をストリーミングするためにChunked Transferを使用できます。Apollo ServerとHasuraはGraphQLサブスクリプションに対してチャンク応答を送信し、イベントが発生するたびに送信します。クライアントはポーリングを必要とせずにリアルタイムの更新を受信します。
Chunked Transferはウェブアプリケーションに重要な利点を提供します。即時データ送信 — サーバーは送信前に応答をバッファリングせず、最初のバイトまでのレイテンシを削減します。ストリーミング処理 — クライアントは完全なダウンロードを待たずにデータが到着次第処理を開始できます。メモリ制限なし — サーバーは応答全体をメモリに保存せず、大量のデータにとって重要です。無限のストリーム送信能力 — SSE、ライブビデオ、モニタリング。
ただし、Chunked Transferには制限があります。オーバーヘッドは各チャンクのサイズ+CRLFで6〜12バイトであり、多数の小さなチャンク(たとえば各100バイト)の場合、応答サイズが10〜15%増加する可能性があります。正確なサイズを指定できない — クライアントは事前にバッファを割り当てたりプログレスバーを表示したりできません。プロキシサーバーの問題 — 一部の古いプロキシはチャンク転送をサポートしておらず、そのような応答をキャッシュできません。ダウンロード再開のサポートなし — 部分的に受信したチャンク応答に対してRangeリクエストを行うことはできません。
HTTP Archive, 2025によると、すべてのHTTP応答の約35%がTransfer-Encoding: chunkedを使用しています。そのうち、動的ページ(60%)、API応答(25%)、メディアストリーム(15%)が大半を占めています。静的ファイルはサイズが事前にわかっているため、ほとんどの場合Content-Lengthを使用します。チャンク応答の割合は、追加のTransfer-Encodingヘッダーを必要とせずにフレームレベルでストリーミングが実装されるHTTP/2の採用に伴い、徐々に減少しています。
モバイル開発では、大容量ファイル(画像、ビデオ)のダウンロードや、大規模なデータ配列を返すAPIリクエストにChunked Transferを使用してください。OkHttpは追加設定なしでチャンク転送を完全にサポートしています。サーバーへのアップロードには、Chunked Transferは使用されません。HTTP/1.1にはアップロード用のTransfer-Encodingがないためです。iOSでは、URLSessionは特別な設定なしでチャンクデータの送受信の両方をサポートしています。ストリーミングJSON解析(Jackson Streaming APIやMoshiなど)により、チャンクストリームで到着する大規模なJSON配列を処理できます。
よくある質問
サーバーは応答サイズが不明な場合に自動的にChunked Transferを有効にします。NginxはContent-Lengthが設定されていない場合にTransfer-Encoding: chunkedを追加します。Spring Bootでは、StreamingResponseBodyとSseEmitterが自動的にチャンク転送を使用します。Node.js Expressでは、Content-Lengthなしでres.write()とres.end()を呼び出すと応答がチャンクになります。
いいえ、HTTP/1.1仕様はContent-LengthとTransfer-Encoding: chunkedの同時使用を禁止しています。サーバーが両方のヘッダーを送信した場合、クライアントはContent-Lengthを無視し、応答をチャンクとして処理する必要があります。このルールはRFC 7230で確立されており、応答本文を変更する可能性のあるプロキシサーバーとの互換性のためです。
最適なチャンクサイズはシナリオによって異なります。通常のWebページの場合は4〜8 KB。ビデオストリーミングの場合は16〜64 KB。SSEの場合はレイテンシを削減するために1〜2 KBの最小チャンク。チャンクサイズはトランスポート層での断片化を最小限に抑えるためにTCPセグメントサイズ(イーサネットの場合は1460バイト)の倍数にする必要があります。
最新のプロキシサーバー(Nginx、HAProxy、Envoy)はChunked Transferをサポートしています。プロキシはバッファリングなしでチャンクを転送(ストリーミング)するか、応答全体をバッファリングしてContent-Lengthとともに再送信できます。古いプロキシはチャンク応答を完了までバッファリングする可能性があり、レイテンシが増加します。HTTP/2はこの問題をプロトコルレベルで解決します。
これらは同じものです。Chunked TransferはHTTP/1.1仕様のメカニズムの完全な名称です。HTTP chunked encodingも同じもので、ライブラリのドキュメントで使用されることがあります。Transfer-Encoding: chunkedはこのモードを有効にするヘッダーです。3つの用語はすべて、データを分割して送信する同じメカニズムを説明しています。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。