Range Requests and Partial Responses

この頁では、リソースの一部分だけを取得できる仕組みである「範囲リクエスト(部分的リクエスト)」と、それに対応する「部分的レスポンス」について解説します。

レンジ単位とは

HTTP/1.1では、レスポンスとして得られるリソースの一部だけを取得することができます。 これにより、たとえば接続途中に通信が切断されるようなアクシデントが起きても、次回のリクエストでは未受信部のみをサーバに要求できるので、帯域や通信時間が節約できます。 まず、RFC 2616のsection 3.12をご覧ください。

HTTP/1.1 では、クライアントはレスポンス内に含まれるレスポンスエンティティの (範囲の) 一部だけを要求できる。 HTTP/1.1 は、Range (section 14.35) と Content-Range (section 14.16) 各ヘッダフィールドにおいてレンジ単位を使用する。 エンティティは、さまざまな構造上の単位に従ったサブレンジへと分解されるだろう。

 range-unit       = bytes-unit | other-range-unit
 bytes-unit       = "bytes"
 other-range-unit = token

HTTP/1.1 で定義されている唯一のレンジ単位は、"bytes" である。 HTTP/1.1 実装は、別の単位を使って指定されたレンジは無視する事ができる

データの一部を指定する時の単位をレンジ単位と呼びます。 HTTP/1.1では、bytesという単位、つまり「1バイト単位」での部分要求だけが定義されていますが、実際にサーバがどのレンジ単位を使うかはAccept-Rangesヘッダにて示されます。 RFC 2616のsection 14.5をご覧ください。

Accept-Ranges レスポンスヘッダフィールドは、サーバにリソースへの範囲リクエストの受け入れを示させる。

 Accept-Ranges     = "Accept-Ranges" ":" acceptable-ranges
 acceptable-ranges = 1#range-unit | "none"

バイトレンジリクエストを受け入れるオリジンサーバは、以下のものを送る事ができる

 Accept-Ranges: bytes

ただし、HTTP/1.1アプリケーションにとって、リソースの部分的要求・応答の実装は必須ではありません。 つまり、ユーザエージェントが部分的要求をしたにもかかわらず、サーバはリソース全体を返すかもしれません。 RFC 2616のsection 3.12及びsection 14.5をご覧ください。

HTTP/1.1 では、アプリケーションの実装がレンジについての知識に依存しないという事が認められる様に設計されている。

リソースへのいかなる範囲リクエストも受け入れないサーバは、以下のものを送る事ができる

 Accept-Ranges: none

これは、クライアントに範囲リクエストをしないように忠告する。

このように、サーバはAccept-Rangesヘッダにnoneというキーワードを含めることによって、自身がバイト範囲をサポートしていないことを通知することができます。

範囲リクエスト(部分的GETリクエスト)

リソースの一部のみをレスポンスとして要求するようなGETリクエストのことを、範囲リクエスト{Range Request}あるいは部分的(GET)リクエスト{Partial (GET) Request}と呼びます。

一般的な範囲リクエスト

範囲リクエストでは、範囲を指定するためにRangeヘッダを付加します。 Rangeヘッダについては、RFC 2616のsection 14.35をご覧ください。

条件付き、あるいは条件の付かない GET メソッドを使った HTTP 検索リクエストは Range リクエストヘッダを使って、エンティティ全体の代わりに、エンティティの一つ以上の sub-range を要求でき、リクエストの結果として返されるエンティティに当たる。

 Range = "Range" ":" ranges-specifier

サーバは、Range ヘッダを無視できる。 しかし、Range をサポートする事で転送に失敗した部分の再取得や、大きなサイズのエンティティの部分的な検索を効果的にサポートするので、HTTP/1.1 のオリジンサーバや中間キャッシュは、可能であればバイトレンジをサポートすべきである。

Rangeヘッダでの具体的な範囲の指定方法は、RFC 2616のsection 14.35.1に記述されています。

バイトレンジ操作では、単一のバイトレンジ、または一つのエンティティ中で複数のレンジセットを指定する事ができる

 ranges-specifier = byte-ranges-specifier
 byte-ranges-specifier = bytes-unit "=" byte-range-set
 byte-range-set  = 1#( byte-range-spec | suffix-byte-range-spec )
 byte-range-spec = first-byte-pos "-" [last-byte-pos]
 first-byte-pos  = 1*DIGIT
 last-byte-pos   = 1*DIGIT

byte-range-spec 中の first-byte-pos 値には、その範囲の最初のバイトの byte-offset を与える。 last-byte-pos 値には、その範囲の最後のバイトの byte-offset を与える。 つまり、指定されるバイトの位置も含まれる。 バイトオフセットは 0 から始まる。

last-byte-pos 値がある場合、その値は byte-range-spec での first-byte-pos 以上のものにしなければならないが、そうで無い場合は構文上不正となる。 一つ以上の構文上不正な byte-range-spec 値を含む byte-range-set を受信しても、その byte-range-set を含むヘッダフィールドは無視しなければならない

last-byte-pos 値が無い、あるいはその値が現在のエンティティボディの大きさ以上であったら、last-byte-pos はエンティティボディの現在のバイト長から 1 を引いた値になる。

クライアントは、自身が指定する last-byte-pos によって、エンティティのサイズを知らなくても受け取るバイト数を制限する事ができる。

 suffix-byte-range-spec = "-" suffix-length
 suffix-length = 1*DIGIT

suffix-byte-range-spec は、エンティティボディの、suffix-length 値によって与えられる長さの末尾を指定するのに使われる。 (すなわち、この形式ではエンティティボディの最後の N バイトを指定する。) もしエンティティが指定された suffix-length 以下の長さしかなければ、エンティティ全体が使われる。

構文上不正な byte-range-set が、first-byte-pos が現在のエンティティボディ長よりも小さいような byte-range-spec、あるいは 0 でない suffix-length を持つ suffix-byte-range-spec を一つでも含んでいたら、その byte-range-set は構文を満足する。 そうでない byte-range-set は不正値である。byte-range-set が不正値である場合、サーバは 416 (Requested range not satisfiable) というステータスを持ったレスポンスを返すべきである。 そうで無ければ、サーバはエンティティボディのうち指定された範囲と 206 (Partial Content) というステータスを持ったレスポンスを返すべきである

byte-ranges-specifier 値の例を示す (エンティティボディの大きさを 10000 と仮定する)。

少し長いですが、要約すると以下のようになります。 以下の例では、エンティティボディ全体の大きさをZバイトとし、a〜dを数値とします。

条件付き範囲リクエスト

範囲リクエストを利用する際、その前提条件となるのは「ユーザエージェントは、すでにリソースの一部を保有しており、かつサーバ上のリソースが、前回取得した時点からまだ更新されていないこと」です。 何故なら、既にクライアントが持っているリソースが、サーバ上のリソースの一部でないのならば、それらの部分同士を繋ぎ合せて一つのリソースを完成させるということができなくなるからです。 このようなことをあらかじめ確認するためには、ユーザエージェントは1回余計にリクエストを発行する必要があります。 (1回目:リソースは更新されているか?,更新されていれば2回目で範囲リクエスト)

しかし、このような確認行為は通信の手間を生じさせ、範囲リクエストを行うメリットを減少させてしまいます。 そこで、そのような手間を省くために、HTTP/1.1では「条件付き範囲リクエスト」という仕組みが用意されており、If-Rangeというヘッダを使って実現します。 RFC 2616のsection 14.27をご覧ください。

もしクライアントがキャッシュとしてあるエンティティのコピーの一部を保持していて、そのエンティティ全体の最新のコピーが欲しい場合、 (If-Unmodified-Since か If-Match、あるいは両方を使った) 条件付き GET として Range リクエストヘッダを使う事ができる。 しかし、もしエンティティが更新されたために条件が成立しなければ、クライアントは最新のエンティティボディ全体を取得するために次なるリクエストをしなければならない。

If-Range ヘッダは、クライアントの次なるリクエストを "短略化{short-circuit}" する事ができる。 一般的に、これは「もしエンティティが更新されていなかったら、私が持っていない部分を送信してくれ。そうでなければ、新しいエンティティ全体を送信してくれ。」のように解釈される。

 If-Range = "If-Range" ":" ( entity-tag | HTTP-date )

クライアントがそのエンティティのエンティティタグは持っていないが、Last-Modified の日付を持っている場合、クライアントは If-Range ヘッダとしてその日付を使う事ができる。 (サーバは、多くても2文字を調べるだけで、正確な HTTP 日付と任意の形式のエンティティタグとを区別する事ができる。) If-Range ヘッダは Range ヘッダと共にのみ使うべきであり、リクエストが Range ヘッダを含んでいない場合や、サーバが sub-range 操作をサポートしない場合には、これは無視されなければならない

If-Range ヘッダにて与えられたエンティティタグがそのエンティティの現在のエンティティタグに一致した場合、サーバは 206 (Partial content) レスポンスを使って、そのエンティティの指定される sub-range を供給すべきである。 もしエンティティタグが一致しなければ、サーバは 200 (OK) レスポンスを使って、そのエンティティ全体を返すべきである

文中にあるように、If-Rangeヘッダを使うと、もしエンティティが更新されていなかったら、私が持っていない部分を送信してくれ。そうでなければ、新しいエンティティ全体を送信してくれ。と解釈されます。 このリクエストに対するレスポンスについては、部分的レスポンスを参照ください。

部分的レスポンス

範囲リクエストに対するレスポンスのことを、部分的レスポンス{Partial Response}と言います。 また、レジューム(※)と呼ばれる場合もあります。

(※) レジュームの本来の意味は、「コンピュータの電源を切る直前の状態を記憶させておき、再度電源を入れた時に状態復帰すること」です。 この場合のレジュームという用法は、この「電源の切断」→「切断直前からの状態復帰」を、「HTTPの切断」→「切断直前からのリクエストやり直し」にたとえたものになります。

部分的レスポンスに成功した場合(206レスポンス)

範囲リクエストに成功した場合、クライアントには206レスポンスが返されます。 RFC 2616のsection 10.2.7をご覧ください。

サーバはリソースに対する部分的 GET リクエストを受け入れた。 リクエストは、望む範囲を示すための Range ヘッダフィールド (section 14.35) を含めなければならないし、またリクエストを条件付きにしたいければ If-Range ヘッダフィールド (section 14.27) を含んだ方がよい

レスポンスは、以下のヘッダフィールドを含めなければならない

もし 206 レスポンスが、強いキャッシュバリディタ (section 13.3.3 参照) を使った If-Range リクエストの結果ならば、レスポンスは他のエンティティヘッダを含めるべきではない。 もし、レスポンスが弱いバリディタを使った If-Range リクエストの結果がとしたら、レスポンスは他のエンティティヘッダを含めてはならない。 これはキャッシュされたエンティティボディと更新されたエンティティヘッダとの不一致を避ける為である。 そうで無ければ、レスポンスは同じリクエストに対して 200 (OK) レスポンスと共に返されたであろうすべてのエンティティヘッダを含めなければならない

もし ETag か Last-Modified ヘッダが正確に一致しなければ、キャッシュは他の以前キャッシュされた要素と 206 レスポンスとを結びつけてはならない。 section 13.5.4 参照。

Range や Content-Range ヘッダをサポートしていないキャッシュは、206 (Partial) レスポンスをキャッシュしてはならない

206レスポンスは、「サーバは、範囲リクエストを受け入れて部分的レスポンスを返しますよ」ということを伝える時に使うステータスコードです。 一般的な範囲リクエストで、Rangeヘッダに誤りがなければ、このレスポンスが返されるでしょう。

(※) ただし、条件付き範囲リクエスト(If-Rangeヘッダを使ったリクエスト)の場合は、クライアント(あるいはキャッシュサーバ)が持つキャッシュと、オリジンサーバ上のリソースとの比較作業が必要となります。 この場合、エンティティが更新されていなければ、206レスポンスと部分的レスポンスが返されますし、リソースが更新されていたら200レスポンスとエンティティ全体が返されることになります。

部分的レスポンスでは、Content-Rangeヘッダが含まれますが、そのヘッダを定義しているRFC 2616のsection 14.16には、部分的レスポンスの例が示されています。

byte-content-range-spec値の例として、エンティティが全体で 1234 バイト持っていると仮定する。 (訳注:byte-content-range-specとは、レスポンスボディの範囲を表すパラメータです)

最初の 500 バイト
bytes 0-499/1234
次の 500 バイト
bytes 500-999/1234
最初の 500 バイト以外すべて
bytes 500-1233/1234
最後の 500 バイト
bytes 734-1233/1234

HTTP メッセージが単一のレンジ (例えば、単一のレンジへのリクエスト、あるいは複数レンジであっても隙間無く重なるようなセットへのリクエスト等へのレスポンス) の内容を含んでいる場合、この内容は Content-Range ヘッダと、実際に転送されるバイト数を示した Content-Length ヘッダを伴う。 例を見よ。

 HTTP/1.1 206 Partial content
 Date: Wed, 15 Nov 1995 06:25:24 GMT
 Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT
 Content-Range: bytes 21010-47021/47022
 Content-Length: 26012
 Content-Type: image/gif

このように、部分的レスポンスではContent-Rangeヘッダの他に、Content-Lengthヘッダも返されます。 Content-Rangeは、リソース全体の大きさと、リソースのどの範囲のデータが送られているかを示しています。 一方、Content-Lengthは、実際に転送されているデータ量を示しています。

部分的レスポンスに失敗した場合(416レスポンス)

範囲リクエストに失敗した場合、クライアントには416レスポンスが返されます。 RFC 2616のsection 10.4.17をご覧ください。

リクエストが Range ヘッダフィールド (section 14.35) を含み、このフィールドの範囲指定値が選ばれたリソースの現在の範囲に重なっていなくて、リクエストに If-Range リクエストヘッダフィールドを含んでいなかったら、サーバはこのステータスコードを含むレスポンスを返すべきである。 (バイトレンジの場合、これはすべての byte-range-spec 値での first-bytes-pos が、現在選択されているリソースの長さを超えている事を意味する。)

このステータスコードをバイトレンジのリクエストで返す場合、レスポンスには選ばれたリソースの現在のサイズを特定するために Content-Range ヘッダフィールドを含むべきである (section 14.16 参照)。 このレスポンスは、content-typemultipart/byteranges のものに使用してはならない

たとえば、1000バイトのリソースに対して、Range: 2000-というリクエストを送った場合、このステータスコードが返されます。 ただし、このリクエストに適切なIf-Rangeが含まれていれば、サーバは「リソース更新前は2000バイト以上あったのだろうが、現在は1000バイトしかないから、リソース全体を返そう」と振る舞うでしょう。

一度に複数の部分的レスポンスを返す場合

Rangeヘッダでは、複数の範囲を指定することができるので、当然部分的レスポンスでは、一度に複数の部分を返すことができます。 RFC 2616のsection 14.1619.2をご覧ください。

HTTPメッセージが複数のレンジ (例えば、その範囲が重ならないような複数レンジへのリクエストのレスポンス) の内容を含んでいる場合、それらはマルチパートメッセージとして転送される。 この目的で使われるマルチパートメディアタイプは、付録 19.2 にて定義される "multipart/byteranges" である。

HTTP 206 (Partial Content) レスポンスメッセージが複数の範囲の内容 (複数の重ならない範囲のリクエストへのレスポンス) を含む時、これらはマルチパートメッセージボディとして転送される。 この目的のためのメディアタイプは "multipart/byteranges" と呼ばれる。

multipart/byteranges メディアタイプは二つ以上の部分を含み、それぞれに自身の Content-TypeContent-Range フィールドを持つ。 要求される境界パラメータはそれぞれのボディ部分を分けるために使われる境界文字列を指定する。

メディアタイプ名multipart
メディアサブタイプ名byteranges
必要なパラメータboundary
省略可能なパラメータなし
エンコーディングについて"7bit", "8bit", "バイナリ" のみが許される。
セキュリティについてなし

例を見よ。

 HTTP/1.1 206 Partial Content
 Date: Wed, 15 Nov 1995 06:25:24 GMT
 Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT
 Content-type: multipart/byteranges; boundary=THIS_STRING_SEPARATES

 --THIS_STRING_SEPARATES
 Content-type: application/pdf
 Content-range: bytes 500-999/8000

 ...最初の範囲...
 --THIS_STRING_SEPARATES
 Content-type: application/pdf
 Content-range: bytes 7000-7999/8000

 ...次の範囲
 --THIS_STRING_SEPARATES--

一つのメッセージ内に複数のデータを含める仕組みをマルチパートタイプと言います。 HTTPでは、複数の部分的レスポンスを返すために、マルチパートタイプを拡張して、multipart/byterangesというメディアタイプを作ったのです。

部分的レスポンスのまとめ

以上の内容をまとめます。 RFC 2616のsection 14.16の残りの部分をご覧ください。

単一レンジへのリクエストのレスポンスには、multipart/byteranges メディアタイプを使って送ってはならない。 結果的に単一レンジになるような、複数レンジへのリクエストのレスポンスには、その部分を送るのに multipart/byteranges メディアタイプを使ってもよい。 multipart/byteranges メッセージをデコードできないクライアントは、一つのリクエストで複数のバイトレンジを要求してはならない

クライアントが1つのリクエストで複数のバイトレンジを要求した時、サーバはリクエストされた順にバイトレンジを返すべきである

サーバが構文上不正だという理由で byte-range-spec を無視した場合、サーバはその不正な Range ヘッダフィールドが存在しない時と同様にそのリクエストを扱うべきである。 (通常、それはエンティティ全体を含んだ 200 レスポンスを返す事を意味する。)

もし、サーバが満足できない Range リクエストヘッダフィールド (つまり、その byte-range-spec 値のすべてにおいて、first-byte-pos の値が選択されたリソースの現在の長さを越えているようなもの) を含んだ (If-Range リクエストヘッダフィールドを含む場合以外の) リクエストを受けたならば、416 (Requested range not satisfiable) というレスポンスコードを返すべきである (section 10.4.17)。

注: すべてのサーバがこのリクエストヘッダを実装しているわけではないので、クライアントは満足できない Range リクエストヘッダフィールドへのレスポンスとして、サーバが 200 (OK) レスポンスの代わりに 416 (Requested range not satisfiable) レスポンスを送るであろうという事を当てにはできない。

部分的レスポンスについてまとめると、以下の様になります。

部分的レスポンスのステータス
範囲リクエストが成功した場合、サーバは206レスポンスを返す
すでにリソースが更新されたなどで、範囲リクエストには失敗したが、リクエストそのものに成功している場合、200レスポンスと完全なエンティティを返す
文法の不正などで、リクエストそのものに失敗した場合、416レスポンスを返す
ただし、サーバは必ずしも範囲リクエストを実装する必要はないので、クライアントは206416が返ってくることを当てにすべきではない
複数の部分的レスポンス
Rangeヘッダで複数の範囲を指定した場合、レスポンスにはmultipart/byterangesというメディアタイプを使う必要がある
この場合、パート毎にContent-TypeContent-Rangeヘッダが追加される
ただし、単一範囲が指定された場合、レスポンスにはmultipart/byterangesを用いてはならない
bytes=500-600,601-999のような「実質的に単一範囲」の場合は、multipart/byterangesを使ってもいいし、使わなくてもよい
エンティティの大きさがわからないときには、*が返される

このように、範囲リクエストに対して、部分的レスポンスを正しく対応させるのはやや難しいです。 しかし、「指定範囲を単一に限定」などと仕様を制限すれば、仕様もシンプルとなり、設計も容易となるでしょう。

参照文献

Webリソース

書籍