Transfer-Encoding: chunkedとHTTP/1.0の関係について

Transfer-Encoding: chunkedとHTTP/1.0について触れる機会があったので、覚書として記述しようと思います。

HTTP/1.0と1.1のストリームデータ転送について

HTTP/1.1におけるストリームデータ転送

Transfer-Encoding: chunked とは、HTTP/1.1において策定されたHTTPヘッダの一種です。
HTTP/1.1では通常、Content-Length を指定しなければなりません。
ストリームデータ等、事前に長さを知ることができない場合に Transfer-Encoding: chunked が使用されます。

HTTP/1.0によるストリームデータ転送

HTTP/1.0には Transfer-Encoding: chunked のような仕組みはありません。
そのため、ストリームデータのような長さを事前に知ることができないデータを転送する場合、単純に Content-Length を使用せず転送を開始する方法が使用されていました。
なぜ Content-Length を指定する必要があるのか、HTTP/1.0における Content-Length を指定しない場合にどういった問題があるのか、この後説明していきます。

Content-Lengthの必要性

Content-Lengthを指定しなかった場合

Content-Length を指定しない場合、データの転送完了を事前に知ることができません。
そのため、 HTTP/1.0において Content-Length が指定されていない場合、TCPコネクションの切断を持ってデータ転送の完了を判断しています。

HTTP/1.0 200 OK
Server: example

Payload
<TCP Connection closed> <<< コネクションが切れたここまででデータ転送完了と判断する

この時の問題点について、次に説明する Content-Length を指定できた場合のメリットでお伝えします。

Content-Lengthを指定した場合

転送されるデータのサイズが事前に分かっていると、2つのメリットがあります。

TCPコネクションを切断せずにデータ転送の完了を知る事ができる

TCPのコネクションを確立する際、3-way ハンドシェイクという方法が用いられます。
これは双方向で合計3回データのやり取りを行わなければならず、複数回HTTPでデータをやり取りする場合処理コストが非常に高くなってしまいます。

HTTP/1.0 200 OK
Server: example
Content-Length: 7 

Payload <<< 7バイトを読み取った時点でデータ転送完了と判断する
HTTP/1.0 200 OK
Server: example
Content-Length: 8

Payload2 <<< 一つのコネクションで引き続き別のデータを転送する事が出来る

例えば一つのWebページでJSやCSSなど10回同じサイトからダウンロードを行うとき、TCPコネクションを毎回確率すると合計30回の余計な通信が発生してしまいます。
一つのコネクションでやり取りできれば、27回のやり取りを削減する事ができます。

データの正常性を判断する事ができる

TCPコネクションの切断においてデータ転送の完了を判断するため、データが転送が完了して切断されたのか、ネットワークの不調によって切断されたのか判断する事ができません。
大きめのデータや不安定な環境でデータのダウンロードを行う場合、不本意にTCPコネクションが切断される場合があります。
この時、データ転送が途中で失敗したにもかかわらず、クライアントは正常にデータのダウンロードが完了したと判断する事になります。

HTTP/1.0 200 OK
Server: example

Payl <TCP Connection closed> <<< 転送が完了していないが、データ転送が正常に完了したと判断される
HTTP/1.0 200 OK
Server: example
Content-Length: 7

Payl <TCP Connection closed> <<< 指定されたサイズより少ないデータでコネクションが切断されたため、不正なデータと判断できる

データのダウンロードが完了したにもかかわらず、データが破損していた時はこのような現象が発生していたと考えられます。

HTTP/1.1 と Transfer-Encoding: chunked

何を解決することができたか

HTTP/1.0 の場合、Content-Length を指定せずTCPコネクションの切断によってデータ転送を完了する方法でしかストリームデータの連携が行えませんでした。
HTTP/1.1 の Transfer-Encoding: chunked を使用することで、前に上げた Content-Length を指定しないデメリットを解消しながらストリームデータの転送が行えるようになりました。

どのように解決したか

Transfer-Encoding: chunked を使用すると、送信するデータの中でチャンクを指定し、データの転送を行います。
データの単位をチャンクにすることで、事前に全量のデータサイズを把握しておく必要はなく、送信するデータのチャンク単位でサイズ(16進数表記)を指定することができます。
チャンクは以下のフォーマットで表現されます。

<ChunkSize(HEX)><CR><LF> <Chunk>
<CR><LF>

このデータを繰り返し送信することで、ストリームデータの転送に対応しています。
また、最後に空のチャンクと改行文字を送信する事でデータの転送完了を判断し、正常に転送が完了したか判断することができます。

ストリームデータを送る一例

ストリームデータを転送するサンプルを用意しました。
可視性向上のため、データ内の改行は<CR><LF> と明記します。

HTTP/1.1 200 OK
Server: example
Transfer-Encoding: chunked

7<CR><LF>
Payload<CR><LF>
8<CR><LF>
Payload2<CR><LF>
0<CR><LF>
<CR><LF>

データの転送が途中で中断される場合

TCPコネクションが途中で切断され、データ転送が完了せずに終了した場合も、正常性の判断が可能です。

HTTP/1.1 200 OK
Server: example
Transfer-Encoding: chunked

7<CR><LF>
Payload<CR><LF>
<TCP Connection closed> <<< 最後の空チャンクが送信されていないため、途中で中断されたと判断できる

後書き

今回は Transfer-Encoding: chunked と HTTP/1.0の関係性について説明しました。
最近ではHTTP/2も主流化し、 Transfer-Encoding: chunked が使用されない時代になってきました。
今後、機会があればHTTP/2の仕様やメリットについて説明したいと思います。