关于本文
本文是通过利用生成式 AI 的自动化生成流程创建的。基于 HTTP Range 规范(RFC 9110),梳理了部分获取的读取方法和确认方法。验证状态:📘 已确认官方规范・未实施实际通信
当你想要从中间开始下载大文件时,HTTP 允许你请求仅需的字节范围而不是“从头到尾全部获取”。这就是 Range 请求和 206 Partial Content 登场的时候。本文将教你如何不混淆 200、206 和 416,并学会根据通信标头(Header)判断发生了什么。
| 项目 | 条件 |
|---|---|
| 对象 | 理解 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 来判断。
更改一个地方并观察失败情况
对于总长度为1000字节的虚构文件,尝试将起始位置从0更改为1000。“Range: bytes=1000-1099”超出了文件末尾,因此通常是416的目标。如果在416的Content-Range中返回“bytes */1000”,则它会成为总长度的线索。但是,实际的服务器响应取决于环境。
这里重要的是,从0开始和包含末尾这两条规则。1000字节的最后一个字节位置是999。如果起始位置是1000,则它不存在。如果计算断点续传的偏移量出错,就会导致最后一个字节重复或缺失。
断点续传时也要注意 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: 必要な断片を管理してから結合
即使图中第一次响应正常,仍有 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 中验证其执行和显示结果。
