HTTP の Range リクエストと 206・416:途中で切れたダウンロードを再開する仕組み
ダウンロードの再開で使う Range・Content-Range・If-Range ヘッダーと、206 Partial Content、416 Range Not Satisfiable のルールを RFC 9110 で整理しました。サーバーが Range を無視する場合も扱います。
10,000 バイトのファイルをダウンロードしていて、最初の 6,000 バイトを受信したところで接続が切れたとします。最初からやり直さずに、残りの 4,000 バイトだけを要求できるようにするのが HTTP の Range リクエストです。ルールは HTTP のセマンティクスを定める RFC 9110 の 14 章にあります。Range リクエストはオプションの機能で、サーバーは Range ヘッダーを無視して通常の GET と同じように全体を返してもかまいません。仕様が Range の処理を定義しているメソッドは GET だけです。
バイト範囲の書き方
Range: bytes=0-499 は先頭の 500 バイトです。バイト位置は 0 から数え、両端を含みます。bytes=6000- のように終わりを省くと 6000 番目のバイトから最後までになり、終わりの値が実際の長さ以上でも最後までと解釈されます。bytes=-500 のように始まりを省くと末尾の 500 バイトで、ファイルがそれより短ければ全体が対象です。gzip などのコンテンツコーディングがかかったレスポンスでは、範囲は展開後ではなくエンコードされたバイト列で数えます。
206 レスポンスと Content-Range
サーバーが範囲を受け付けると、206 Partial Content で応答します。
HTTP/1.1 206 Partial Content
Content-Range: bytes 6000-9999/10000
Content-Length: 4000
Content-Range は 始まり-終わり/全体の長さ の形で、全体の長さが分からないときは /* を使います。Content-Length はこのメッセージ本文の長さで、ファイル全体の長さではありません。サーバーは要求された範囲の一部だけを返すこともあるので、クライアントは Content-Range を見て何を受け取ったのかを確かめる必要があります。
bytes=0-99,5000-5099 のように複数の範囲を要求し、サーバーが複数のパートを返す場合、本文は multipart/byteranges 形式になります。Content-Range は最上位のヘッダーではなく各パートに入ります。サーバーは重なる範囲や近い範囲をまとめることができます。複数のパートは要求順に送ることが推奨されていますが、まとめられた範囲や満たせない範囲は除かれるため、要求した一覧と一対一で対応するとは限りません。範囲を一つだけ要求した場合、サーバーは multipart で応答してはいけません。
416 と 200
要求した範囲がどれも満たせなければ 416 Range Not Satisfiable です。バイト範囲が満たせるのは、開始位置が現在の長さより小さいか、長さが 0 でない末尾指定の範囲である場合です。長さ 10,000 のファイルに bytes=10000- を要求しても満たせないので、サーバーは Content-Range: bytes */10000 で現在の長さを伝えることが推奨されています。小さな範囲を極端に多く要求したり、重なる範囲を要求したりした場合も 416 で拒否できます。仕様はこうしたリクエストを、壊れたクライアントかサービス拒否攻撃の兆候とみなしています。
ただし仕様自身が、こうした場合に多くの実装は全体を 200 で返すため、クライアントは 416 を当てにできないと書いています。サーバーの設定もまちまちです。nginx の max_ranges はデフォルトでは上限がなく、上限を超えたリクエストは範囲指定がないものとして扱い、0 にすると範囲のサポートを無効にします。Apache HTTP Server の MaxRanges はデフォルトが 200 で、超えると全体を返します。Range を送る側は 206、200、416 のどれが返っても処理できるようにしておく必要があります。
If-Range:その間にファイルが変わっていたら
6,000 バイトを受け取ったあとでサーバーのファイルが新しい版に差し替えられていたら、つなぎ合わせた結果はどちらの版でもありません。仕様では、断片を結合できるのは同じ強いバリデーター(strong validator)を持つ場合だけです。この確認と再リクエストを 1 回で済ませるヘッダーが If-Range です。
GET /file.zip HTTP/1.1
Range: bytes=6000-
If-Range: "v1"
ETag が "v1" のままで、サーバーが Range を処理する場合は、残りが 206 で届きます。値が異なる場合、サーバーは Range を必ず無視します。Range がなければ 200 になる GET であれば、新しい版の全体が 200 で返ります。ETag は強い比較で照合され、W/ で始まる弱い ETag は If-Range に使えません。Last-Modified の日付を使えるのは ETag がなく、その日付が強いバリデーターである場合だけで、完全に一致したときに真となります。
Accept-Ranges: bytes は案内にすぎません。このヘッダーを受け取ったからといって、次の Range リクエストが 206 で返ってくると考えてはいけません。キャッシュは 206 レスポンスを未完了の 200 レスポンスとして保存できますが、部分的なレスポンスをクライアントに返すときは必ず 206 と明示しなければなりません(RFC 9111)。ETag と 304 については no-cache と no-store の違いで取り上げました。
参照文書
- RFC 9110 HTTP Semantics:8.8.3、13.1.5、14、15.3.7、15.5.17、17.15 節
- RFC 9111 HTTP Caching:3.3、3.4 節
- nginx
ngx_http_core_module:max_ranges - Apache HTTP Server 2.4 core:
MaxRanges
リンクと内容は2026年10月5日に確認しました。