この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。HTTP Rangeの仕様(RFC 9110)をもとに、部分取得の読み方と確認方法を整理しました。検証ステータス:📘 公式仕様確認済み・実通信は未実施
大きなファイルを途中からダウンロードしたいとき、HTTPでは「先頭から全部」ではなく必要なバイト範囲だけを要求できます。そこで登場するのがRange要求と206 Partial Contentです。この記事では、200・206・416を混同せず、通信ヘッダーから何が起きたかを判断する方法を説明します。
| 項目 | 条件 |
|---|---|
| 対象 | HTTP/1.1以降のRange要求を理解する読者 |
| 用意するもの | curlと公開されているRange対応のテスト用ファイル |
| 費用 | 追加APIキー・有料サービスは不要 |
| 検証 | RFC 9110の仕様を確認。特定サーバーへの実通信は未実施 |
| 注意 | Rangeを送っても必ず206になるわけではない |
まず試す:先頭100バイトを要求する
権利と利用条件を確認した公開テストファイルのURLを一つ選びます。URLには認証トークンや署名付きクエリを含めないでください。次は読者が実行するための例で、執筆時に実行成功した記録ではありません。
curlの通常GETでHTTPステータスとContent-Lengthを確認します。HEADの結果はGETと同一とは限らないため、実際のGETの応答も確認します。
同じURLに対してcurlの「–range 0-99」オプションを付けて要求し、レスポンスヘッダーと受信バイト数を確認します。
206ならContent-Rangeを読み、要求した範囲と返された範囲が一致するか確認します。200ならサーバーがRangeを無視して全体を返した可能性があります。
例えば説明用の架空ファイルが1000バイトなら、Rangeヘッダーは「Range: bytes=0-99」、206応答のContent-Rangeは「bytes 0-99/1000」のようになります。開始位置も終了位置も含むため、0から99は100バイトです。これは予想例であり、実サイトで観測した出力ではありません。
206が返る条件と、200になる条件
RFC 9110のRangeはGETに対して適用されます。サーバーはRangeヘッダーを無視してもよく、その場合は通常の200 OKと全体の本文が返ることがあります。したがって「206ではない=サーバー障害」ではありません。
| 応答 | 意味 | 次に確認すること |
|---|---|---|
| 200 OK | 全体の表現が返ることがある | Rangeが無視されたか、要求先は正しいか |
| 206 Partial Content | 選択した範囲が返る | Content-Range、長さ、Content-Type |
| 416 Range Not Satisfiable | 要求範囲を満たせない | 現在の全長、指定した開始位置 |
| 304 Not Modified | 条件付き取得の再検証 | キャッシュ用の条件付きヘッダー |
206は成功応答ですが、全ファイルの取得成功とは異なります。部分データをファイルとして利用するには、欠けている部分の管理や結合、ファイルの整合性確認が必要です。動画プレーヤーやダウンロード再開機能は、こうした部分取得を利用する場合があります。
Content-Rangeを読み解く
「Content-Range: bytes 200-299/1000」は、全長1000バイトの表現から200〜299の100バイトを返したという意味です。単位bytesは文字数ではありません。UTF-8の日本語など複数バイトの文字の途中で切れる可能性があり、部分取得の結果をそのまま文字列として読むのは適切とは限りません。
複数の範囲を一度に要求する場合、応答はmultipart/byteranges形式になることがあります。単純な「一つのContent-Rangeと一つの本文」という前提のプログラムでは扱えません。最初の実験は単一範囲に限定すると切り分けやすくなります。
Accept-Ranges: bytesがあれば範囲要求をサポートする目安になります。ただし、このヘッダーがないことだけで範囲取得不可とは断定できません。実際のGET応答とContent-Rangeで判断します。
1か所変えて失敗を観察する
全長1000バイトの架空ファイルに対して、開始位置を0から1000へ変更してみます。「Range: bytes=1000-1099」はファイル末尾を超えるため、通常は416の対象です。416のContent-Rangeに「bytes */1000」と返る場合は、全長の手がかりになります。ただし実際のサーバーの応答は環境に依存します。
ここで重要なのは、0始まりと末尾を含むという二つの規則です。1000バイトの最終バイト位置は999です。開始位置が1000なら存在しません。ダウンロード再開のオフセット計算を誤ると、最後の1バイトが重複したり欠けたりする原因になります。
ダウンロード再開ではIf-Rangeにも注意
途中まで保存したファイルを再開するとき、サーバー上の内容が更新されている可能性があります。古いファイルの前半と新しいファイルの後半を単純に結合すると、破損したファイルができる恐れがあります。
If-Rangeは、ETagまたは日時の検証条件が満たされる場合に範囲取得を行い、条件が合わなければ全体を取得するための仕組みです。強いETagと弱いETagの扱いには制約があります。単にRangeを付けるだけでは、再開後のファイルの同一性は保証されません。実務ではダウンロード完了後に信頼できるチェックサムで全体を検証する設計も検討してください。
うまくいかないときの切り分け
206が出ない場合は、URLのリダイレクト、GETかHEADか、CDN・プロキシ、圧縮転送の有無、要求と応答の両方を確認します。Content-Encodingによって転送表現が変わると、手元のファイル位置とサーバーの表現上のバイト位置を同一視できないことがあります。無理に部分データを結合しないでください。
416が出た場合は、保存済みサイズとサーバー側の現在の全長を比較します。対象ファイルが変化していないか、Rangeの開始位置が全長以上になっていないかを確認します。リトライを無限に繰り返すのではなく、条件を見直して必要なら最初から取得し直します。
運用監視では206をエラー扱いしない一方、期待していた全体取得が206で終わった場合にはアプリケーション側の処理を確認します。HTTPとして成功していても、アプリが必要とするデータが揃っているとは限りません。
図で追う:Rangeを付けたGETはどう返ってくる?
1,000バイトの架空ファイルから先頭100バイトだけを取得する例を考えます。206 Partial ContentはHTTP通信として成功した応答ですが、「ファイル全体の保存に成功した」という意味ではありません。最初の断片だけが必要なプレビュー機能と、最後まで完全にダウンロードしたい処理では、成功と判断する条件が異なります。
sequenceDiagram
participant C as クライアント
participant S as サーバー
C->>S: GET と Range bytes=0-99
S-->>C: 206 と Content-Range bytes 0-99/1000
Note over C: 先頭100バイトを取得
C->>S: GET と Range bytes=100-199
S-->>C: 206 と Content-Range bytes 100-199/1000
Note over C: 必要な断片を管理してから結合
図の1回目の応答が正常でも、まだ残り900バイトがあります。複数回に分けて取得する場合、受信した部分の開始位置と終了位置、保存先の位置が一致しているか確認する必要があります。0から99までが100バイトになるのは、開始位置を0と数え、終了位置を含めるためです。範囲の計算を間違えると、1バイトだけ重複したり欠けたりします。
curlの結果をどう読み取るか
実験する場合、読者が管理する、または調査を許可されたRange対応の公開ファイルを用意します。次のURLは置換用の説明例であり、実在するファイルの所在や動作は確認していません。実行結果が成功したという主張でもありません。認証情報や署名付きURLはログへ残さないようにします。
# 自分で確認した公開テストファイルのURLを指定する URL='https://example.org/replace-with-your-test-file.bin' curl --show-error --location --range 0-99 --dump-header headers.txt --output first100.bin "$URL" wc -c first100.bin
調べる順序は、最終応答のHTTPステータス、Content-Range、保存されたファイルのバイト数です。リダイレクトがある場合、ヘッダー保存先には複数のHTTP応答が記録されることがあります。最初に現れたステータスだけを読んで判断せず、実際に本文を返した最終応答の条件を確認します。保存ファイルが100バイトだったという事実だけでは、Rangeが処理された証拠になりません。元ファイルがたまたま100バイトで、サーバーが200を返した可能性もあるからです。
206の場合はContent-Rangeの開始・終了位置と実際の受信長を照合します。200で全体が返されたなら、既存ファイルの末尾へ追記せず、全体の応答として扱う必要があります。416が返れば、開始位置がサーバー上の現在の全長を超えていないか確認します。ステータス番号だけでなく、要求したバイト範囲と返されたデータの意味を組み合わせると、通信のどこに問題があるか考えやすくなります。
flowchart TD
A["Range付きGETの最終応答"] --> B{"ステータス"}
B -->|"206"| C["Content-Rangeと受信長を照合"]
B -->|"200"| D["範囲を無視した全体応答の可能性"]
B -->|"416"| E["開始位置と現在の全長を確認"]
B -->|"その他"| F["認証・経路・サーバーを別途調査"]
C --> G["必要な断片がそろったか確認"]
D --> G
E --> H["条件を直してから再要求"]
この分岐図は障害原因を自動判定するものではなく、調査の順序を示しています。例えば416が出た場合、実際にファイルの内容が更新されて全長が短くなった可能性もあります。保存済みの断片が現在のサーバー上のファイルと同じ版かどうか分からなければ、無理に再開してはいけません。
仕事で使うなら「再開できた」と「正しく完成した」を分ける
数GBの配布ファイルを途中までダウンロードし、時間を置いて再開するとします。サーバー側でファイルが同じ名前の新しい版へ更新されていれば、前半は古い版、後半は新しい版という不整合が起こり得ます。Rangeによって残りを受信できたことだけでは、ファイル全体が正しいとは言えません。If-Rangeなどによる版の条件確認や、配布元の信頼できるチェックサムでの最終照合が必要になる場合があります。
HTTPで206は成功でも、アプリケーションが必要とするデータが不足していたら業務上は未完了です。逆に動画配信やプレビューのように一部分だけ必要な処理なら、全体を取得しないことが狙いです。Webアクセスログの206を一律エラー扱いすると、意図的な部分取得まで障害として数えてしまう可能性があります。どの用途でRangeを付けたのかが判断の前提になります。
もう一つ見落としやすいのは、日本語のテキストです。HTTPの範囲は文字数ではなくバイト単位で指定します。UTF-8では日本語の一文字が複数バイトになるため、部分取得した断片が文字の途中で切れる場合があります。そのままデコードして表示するのではなく、欠けたバイト列がないか、区切り位置をどう扱うかを考える必要があります。
さらにプロキシやCDNが圧縮転送を行う構成では、受信したデータと保存対象の表現の関係も確認しなければなりません。最初にContent-EncodingやContent-Rangeを記録し、意図しない変換が行われていないか確かめます。ネットワークの応答だけを見てファイルの完全性を保証できるわけではありません。
まとめ
Rangeは「どこからどこまで欲しいか」、206は「その部分を返した」、Content-Rangeは「実際にどの部分を返したか」を示します。200や416も正常な仕様上の分岐として理解し、HTTPステータスだけでなく応答ヘッダーと受信バイト数をセットで確認すると原因を切り分けやすくなります。
公式資料
この記事の通信例は仕様に基づく架空例です。特定のcurlバージョン、Webサーバー、WordPressでの実行・表示結果は未確認です。
