<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Downloads · XB 现场笔记</title><link>https://hk.xbcatm.com/zh/tags/downloads/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://hk.xbcatm.com/zh/tags/downloads/index.xml" rel="self" type="application/rss+xml"/><item><title>范围请求应证明选中了哪些字节</title><link>https://hk.xbcatm.com/zh/notes/range-requests-should-prove-which-bytes-were-selected/</link><pubDate>Mon, 06 Jul 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/range-requests-should-prove-which-bytes-were-selected/</guid><description>验证部分响应、Content-Range，以及无法满足的字节范围的行为。</description><content:encoded>&lt;p&gt;范围请求让客户端请求表示的一部分。媒体播放器、可断点续传的下载工具与诊断客户端会用到它，但服务器如果错误地声明或返回范围，就可能产生静默损坏，而不是干净的失败。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/range-request.detail.zh.svg" alt="稳定的编码后表示共 N 字节，提供参考大小。标尺选中含端点的 0 到 15，恰好 16 字节。分支区分匹配正文与 Content-Range 的 206、忽略 Range 时的完整 200，以及从 N 开始的越界请求被拒绝时的 416 和 bytes */N。强 If-Range 验证器防止拼接不同版本。禁止弱 ETag；只有无 ETag 且日期满足强验证器条件时才能使用 HTTP 日期。表示改变时须返回完整的最新正文。" width="720" height="3050"&gt;&lt;figcaption&gt;保持表示与内容编码一致，同时核对区间、总大小与实际正文字节。忽略 Range 时，完整的 200 可以合法；续传需要合适的强验证器。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3 id="建立完整大小"&gt;建立完整大小&lt;/h3&gt;
&lt;p&gt;选择至少 16 字节、测试期间不变的下载文件。在同一个一次性 shell 会话和临时工作目录中依次运行以下片段，保留 &lt;code&gt;url&lt;/code&gt; 与 &lt;code&gt;size&lt;/code&gt;。显式的 &lt;code&gt;set -eu&lt;/code&gt; 会让顺序执行的传输与字节断言失败时停止；HTTP 状态与范围响应头仍需按下文人工核对：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -eu
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://www.example.test/downloads/archive.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -fsS -H &lt;span class="s1"&gt;&amp;#39;Accept-Encoding: identity&amp;#39;&lt;/span&gt; -D full.headers -o full.body &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;wc -c &amp;lt; full.body&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$size&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -ge &lt;span class="m"&gt;16&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;awk &lt;span class="s1"&gt;&amp;#39;tolower($1) ~ /^(content-length:|content-encoding:|etag:|last-modified:|accept-ranges:)$/ { print }&amp;#39;&lt;/span&gt; full.headers
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;确认完整响应为 &lt;code&gt;200&lt;/code&gt;，且没有意外的内容编码。这些命令请求 identity 表示，不做自动解压；字节范围针对所选的编码后表示，因此改变编码会使比较失效。&lt;code&gt;Content-Length&lt;/code&gt; 可能缺失，所以用完整正文确定大小。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Accept-Ranges: bytes&lt;/code&gt; 是显式信号，但缺少这个头并不能证明范围请求不受支持。去实际请求一次。&lt;/p&gt;
&lt;h3 id="请求一个已知区间"&gt;请求一个已知区间&lt;/h3&gt;
&lt;p&gt;请求开头 16 字节，同时保留响应头与正文：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -eu
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS -D range.headers -o range.body &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Accept-Encoding: identity&amp;#39;&lt;/span&gt; -H &lt;span class="s1"&gt;&amp;#39;Range: bytes=0-15&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wc -c range.body
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;head -c &lt;span class="m"&gt;16&lt;/span&gt; full.body &amp;gt; expected.body
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cmp expected.body range.body
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;awk &lt;span class="s1"&gt;&amp;#39;tolower($1) ~ /^(content-range:|content-length:|etag:)$/ || /^HTTP\// { print }&amp;#39;&lt;/span&gt; range.headers
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;成功的字节范围响应应使用 &lt;code&gt;206 Partial Content&lt;/code&gt;。&lt;code&gt;Content-Range&lt;/code&gt; 应指出 &lt;code&gt;0-15&lt;/code&gt; 与完整表示长度，而正文应正好包含 16 个字节。&lt;/p&gt;
&lt;p&gt;这里的字节比较专门检查部分正文。合法但大于 16 字节的完整 &lt;code&gt;200&lt;/code&gt; 会使比较失败并停止当前 shell 会话；先查看已保存的响应头，再判断是否发生损坏。反过来，完整文件恰好为 16 字节时，即便返回 &lt;code&gt;200&lt;/code&gt; 也可能通过 &lt;code&gt;cmp&lt;/code&gt;，所以仍需核对状态码与 &lt;code&gt;Content-Range&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="测试一个不可能的区间"&gt;测试一个不可能的区间&lt;/h3&gt;
&lt;p&gt;从完整字节数开始请求：大小为 N 的文件，最后一个有效偏移量是 N−1。与任意写一个大常量不同，这确实超出了已知表示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -eu
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS -D - -o /dev/null &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Accept-Encoding: identity&amp;#39;&lt;/span&gt; -H &lt;span class="s2"&gt;&amp;#34;Range: bytes=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;服务器用 &lt;code&gt;416 Range Not Satisfiable&lt;/code&gt; 拒绝无法满足的字节范围时，应附带 &lt;code&gt;Content-Range&lt;/code&gt;（例如 &lt;code&gt;bytes */12345&lt;/code&gt;）报告当前大小。当服务器忽略 Range 时返回 &lt;code&gt;200&lt;/code&gt; 也可能合法，但它必须返回完整表示，而不是一个标签错误的片段。&lt;/p&gt;
&lt;h3 id="让验证器留在故事里"&gt;让验证器留在故事里&lt;/h3&gt;
&lt;p&gt;续传工具可以用 &lt;code&gt;If-Range&lt;/code&gt; 配合强 ETag。不能使用弱 ETag；只有在没有 ETag、且日期符合强验证器条件时，才能使用 HTTP 日期。如果表示已改变，安全响应是完整的最新表示，而不是从两个版本拼接出的字节。在宣称某个下载&amp;quot;可续传&amp;quot;之前，先测试这一行为。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html#name-range-requests"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，范围单位、部分响应与 &lt;code&gt;Content-Range&lt;/code&gt;，已核验 2026-07-06。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://curl.se/docs/manpage.html"&gt;curl manual&lt;/a&gt;，&lt;code&gt;--range&lt;/code&gt; 与响应头选项，已核验 2026-07-06。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>