<?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>Http · XB 现场笔记</title><link>https://www.xbcatm.com/zh/tags/http/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/http/index.xml" rel="self" type="application/rss+xml"/><item><title>Content-Type 应描述你实际发送的字节</title><link>https://www.xbcatm.com/zh/notes/content-type-should-describe-the-bytes-you-actually-send/</link><pubDate>Sun, 23 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/content-type-should-describe-the-bytes-you-actually-send/</guid><description>在公开边缘核对媒体类型，并用 nosniff 让不匹配以可见的方式失败。</description><content:encoded>&lt;p&gt;静态服务器可能返回正确的字节，却带上错误的 &lt;code&gt;Content-Type&lt;/code&gt;。某个浏览器里页面看起来或许还正常，但下载、样式表、订阅阅读器或安全策略会把同一响应理解成别的东西。公开响应头才是契约；文件名只是服务器配置的输入。&lt;/p&gt;
&lt;h2 id="检查有代表性的格式"&gt;检查有代表性的格式&lt;/h2&gt;
&lt;p&gt;检查一份 HTML 文档、样式表、SVG、feed 与可下载脚本。包含响应头但丢弃正文：&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="k"&gt;for&lt;/span&gt; path in / /assets/site.css /favicon.svg /index.xml /downloads/check-http-headers.sh&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&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="s2"&gt;&amp;#34;https://www.example.test&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk -v &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; BEGIN { print &amp;#34;== &amp;#34; path }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; /^HTTP\// || tolower($1) == &amp;#34;content-type:&amp;#34; || tolower($1) == &amp;#34;x-content-type-options:&amp;#34; { print }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;期望的媒体类型因资源而异，但应该具体且稳定。HTML 通常应包含字符集。CSS 与 SVG 不应被当作泛型二进制数据提供服务。用于下载的 shell 脚本不应被声明为 HTML。&lt;/p&gt;
&lt;h3 id="保持-nosniff-一致"&gt;保持 nosniff 一致&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;X-Content-Type-Options: nosniff&lt;/code&gt; 请求支持它的浏览器在脚本与样式目标上尊重声明的类型，而不是猜测。它之所以有用，是因为不匹配会变成可见的失败，而不是因浏览器而异的解读。&lt;/p&gt;
&lt;p&gt;该响应头并不能修复一个错误的类型。只有在同时有能证明声明值正确的资源清单时才启用它：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X-Content-Type-Options: nosniff
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="检查服务器映射"&gt;检查服务器映射&lt;/h3&gt;
&lt;p&gt;静态服务器通常根据扩展名推导类型。如果某个新扩展名被当作 &lt;code&gt;application/octet-stream&lt;/code&gt; 提供服务，先判断这是否有意为之，再决定是否加入全局覆盖。一个窄映射比一条会改变所有响应的规则更容易复核。&lt;/p&gt;
&lt;p&gt;在公开主机名上重复这些检查。CDN 或反向代理可能替换掉正确的源站响应头；而一个友好的错误页可能为后缀像 CSS 或 JavaScript 的路径返回 HTML。&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"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，表示元数据与 &lt;code&gt;Content-Type&lt;/code&gt;，已核验 2026-08-23。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Type"&gt;MDN: &lt;code&gt;Content-Type&lt;/code&gt;&lt;/a&gt;，媒体类型与字符集示例，已核验 2026-08-23。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Content-Type-Options"&gt;MDN: &lt;code&gt;X-Content-Type-Options&lt;/code&gt;&lt;/a&gt;，&lt;code&gt;nosniff&lt;/code&gt; 行为，已核验 2026-08-23。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Cookie 作用域应比应用边界更窄</title><link>https://www.xbcatm.com/zh/notes/cookie-scope-should-be-narrower-than-the-application-boundary/</link><pubDate>Sun, 23 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/cookie-scope-should-be-narrower-than-the-application-boundary/</guid><description>把 Domain、Path、Secure、HttpOnly 与 SameSite 当作同一份浏览器存储契约来复核。</description><content:encoded>&lt;p&gt;Cookie 依据浏览器规则附加到请求，而不是依据最初设置它的服务器组件。宽泛的 &lt;code&gt;Domain&lt;/code&gt; 或 &lt;code&gt;Path&lt;/code&gt; 会把状态暴露给无关的路由或子域名，而缺失传输与脚本限制则会放大其他失败造成的损害。&lt;/p&gt;
&lt;h2 id="偏好仅主机-cookie"&gt;偏好仅主机 Cookie&lt;/h2&gt;
&lt;p&gt;当省略 &lt;code&gt;Domain&lt;/code&gt; 时，Cookie 被限定在设置它的主机内。加上 &lt;code&gt;Domain=example.test&lt;/code&gt; 则它也允许发给符合条件子域名。只有多个主机确实共享同一应用与信任边界时才使用更宽的形式。&lt;/p&gt;
&lt;p&gt;检视响应而不把真实 Cookie 值打进日志：&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;curl -sS -D - -o /dev/null https://app.example.test/login &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; tolower($1) == &amp;#34;set-cookie:&amp;#34; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; sub(/Set-Cookie: [^;]*/, &amp;#34;Set-Cookie: [value redacted]&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; print
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="一起复核这些属性"&gt;一起复核这些属性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Secure&lt;/code&gt; 把传输限制在安全上下文中。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HttpOnly&lt;/code&gt; 通过 &lt;code&gt;document.cookie&lt;/code&gt; 阻止 JavaScript 访问；它不会阻止浏览器发送该 Cookie。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SameSite&lt;/code&gt; 影响跨站请求，必须匹配应用的导航或嵌入需求。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Path&lt;/code&gt; 控制发送时机，但它不是同一主机上不同应用之间的授权边界。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在现代浏览器中，&lt;code&gt;SameSite=None&lt;/code&gt; 的 Cookie 还需要 &lt;code&gt;Secure&lt;/code&gt;。避免把这对组合复制进不需要跨站用途的应用。&lt;/p&gt;
&lt;h2 id="在其约束契合时使用前缀"&gt;在其约束契合时使用前缀&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;__Secure-&lt;/code&gt; 与 &lt;code&gt;__Host-&lt;/code&gt; 等 Cookie 名称前缀，让支持的浏览器强制额外的要求。&lt;code&gt;__Host-&lt;/code&gt; Cookie 必须使用 &lt;code&gt;Secure&lt;/code&gt;、省略 &lt;code&gt;Domain&lt;/code&gt;、并使用 &lt;code&gt;Path=/&lt;/code&gt;，这使主机专用的意图变得显式。&lt;/p&gt;
&lt;h2 id="用相同作用域测试删除"&gt;用相同作用域测试删除&lt;/h2&gt;
&lt;p&gt;删除 Cookie 需要一条与存储它所用属性匹配的过期指令，尤其是名称、域名与路径。只清除一个变体的登出可能让另一个 Cookie 仍然有效。&lt;/p&gt;
&lt;h2 id="让静态站点免-cookie"&gt;让静态站点免 Cookie&lt;/h2&gt;
&lt;p&gt;一个真正静态的文档站点通常没有理由设置应用 Cookie。核验公共响应不含 &lt;code&gt;Set-Cookie&lt;/code&gt; 响应头，而不是为站点并不需要的存储增加一个同意界面。&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/rfc6265.html"&gt;RFC 6265 — HTTP State Management Mechanism&lt;/a&gt;，Cookie 存储与作用域，已核验 2026-08-23。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie"&gt;MDN: &lt;code&gt;Set-Cookie&lt;/code&gt;&lt;/a&gt;，属性与名称前缀，已核验 2026-08-23。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>HEAD 应描述与 GET 相同的表示</title><link>https://www.xbcatm.com/zh/notes/head-should-describe-the-same-representation-as-get/</link><pubDate>Sun, 23 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/head-should-describe-the-same-representation-as-get/</guid><description>比较 HEAD 与 GET 的元数据，不要把"缺少正文"误当成"资源不同"。</description><content:encoded>&lt;p&gt;&lt;code&gt;HEAD&lt;/code&gt; 方法请求服务器对 &lt;code&gt;GET&lt;/code&gt; 会返回的响应，但不会传输响应内容。它适合需要状态、验证器、类型或长度的检查，但前提是元数据与真实的 &lt;code&gt;GET&lt;/code&gt; 路径保持一致。&lt;/p&gt;
&lt;h2 id="比较规范化后的响应头集合"&gt;比较规范化后的响应头集合&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Date&lt;/code&gt; 这类动态响应头会随请求不同而变化。只比较描述所选表示的那些字段：&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="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://www.example.test/downloads/manual.pdf
&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;curl -sS -I &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; &amp;gt; head.headers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS -D get.headers -o /dev/null &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;&lt;span class="k"&gt;for&lt;/span&gt; file in head.headers get.headers&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;== %s\n&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&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; awk &lt;span class="s1"&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; /^HTTP\// ||
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; tolower($1) ~ /^(content-type:|content-length:|content-encoding:|cache-control:|etag:|last-modified:|vary:)$/ { print }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&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="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;两次请求应选择相同的状态码与表示元数据。压缩协商也应纳入比较：给两个请求发送相同的 &lt;code&gt;Accept-Encoding&lt;/code&gt; 值。&lt;/p&gt;
&lt;h3 id="不要把-head-当作唯一的发布检查"&gt;不要把 HEAD 当作唯一的发布检查&lt;/h3&gt;
&lt;p&gt;服务器可能在文件缺失、不可读或被截断时仍合成看似合理的响应头。在冒烟测试里保留一个小的 &lt;code&gt;GET&lt;/code&gt; 并核验其正文或校验和。&lt;code&gt;HEAD&lt;/code&gt; 降低了传输成本，但并不能证明有效载荷存在。&lt;/p&gt;
&lt;h3 id="显式检查重定向"&gt;显式检查重定向&lt;/h3&gt;
&lt;p&gt;不使用 &lt;code&gt;--location&lt;/code&gt; 时，&lt;code&gt;curl -I&lt;/code&gt; 报告第一个响应，适合测试重定向策略；使用 &lt;code&gt;--location&lt;/code&gt; 时报告最终链，适合测试目标地址。请记录这条命令回答的是哪个问题：&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;curl -sS -I --max-redirs &lt;span class="m"&gt;0&lt;/span&gt; https://old.example.test/manual
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS -I --location https://old.example.test/manual
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="把方法相关的失败视为配置缺陷"&gt;把方法相关的失败视为配置缺陷&lt;/h3&gt;
&lt;p&gt;一个对 &lt;code&gt;GET&lt;/code&gt; 返回 &lt;code&gt;200&lt;/code&gt;、对 &lt;code&gt;HEAD&lt;/code&gt; 返回 &lt;code&gt;405&lt;/code&gt; 或 &lt;code&gt;404&lt;/code&gt; 的静态页面，通常暴露出代理或路由的不一致。应当修复方法处理，而不是教监控系统忽略它——除非端点契约刻意不允许 &lt;code&gt;HEAD&lt;/code&gt;。&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-head"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，HEAD 方法语义，已核验 2026-08-23。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://curl.se/docs/manpage.html"&gt;curl manual&lt;/a&gt;，&lt;code&gt;--head&lt;/code&gt;、&lt;code&gt;--include&lt;/code&gt; 与重定向选项，已核验 2026-08-23。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>范围请求应证明选中了哪些字节</title><link>https://www.xbcatm.com/zh/notes/range-requests-should-prove-which-bytes-were-selected/</link><pubDate>Sun, 23 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.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;h3 id="建立完整大小"&gt;建立完整大小&lt;/h3&gt;
&lt;p&gt;抓取完整表示的响应头，并记录其长度与验证器：&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="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 -sS -D full.headers -o /dev/null &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;awk &lt;span class="s1"&gt;&amp;#39;tolower($1) ~ /^(content-length:|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;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;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;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;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;h3 id="测试一个不可能的区间"&gt;测试一个不可能的区间&lt;/h3&gt;
&lt;p&gt;请求超出已知末尾的字节：&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;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;Range: bytes=999999999-&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对无法满足的字节范围，&lt;code&gt;416 Range Not Satisfiable&lt;/code&gt; 加一个类似 &lt;code&gt;bytes */12345&lt;/code&gt; 的 &lt;code&gt;Content-Range&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; 配合强验证器或日期。如果表示已改变，安全响应是完整的最新表示，而不是从两个版本拼接出的字节。在宣称某个下载&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-08-23。&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-08-23。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>缓存头是两套时钟的契约</title><link>https://www.xbcatm.com/zh/notes/cache-headers-are-a-contract-with-two-clocks/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/cache-headers-are-a-contract-with-two-clocks/</guid><description>根据表示内容可能变化的频率，选择新鲜度与重校验规则。</description><content:encoded>&lt;p&gt;缓存不是一个单一开关。一个响应既有一个由缓存作出的新鲜度决策，也有与源站之间的校验对话。把这两种想法混在一起，会得到陈旧内容或多余请求。&lt;/p&gt;
&lt;h3 id="先选变更模型"&gt;先选变更模型&lt;/h3&gt;
&lt;p&gt;对于发布之后可能变化的 HTML 文档，&lt;code&gt;Cache-Control: no-cache&lt;/code&gt; 通常比 &lt;code&gt;no-store&lt;/code&gt; 更接近正确起点。&lt;code&gt;no-cache&lt;/code&gt; 允许存储，但要求缓存复用前先校验；&lt;code&gt;no-store&lt;/code&gt; 则要求缓存完全不存储。当你想要条件请求与廉价的 &lt;code&gt;304 Not Modified&lt;/code&gt; 时，这个区别很关键。&lt;/p&gt;
&lt;p&gt;对类似 &lt;code&gt;app.4f2c.js&lt;/code&gt; 这种带指纹的资源，URL 在字节变化时也随之改变，此时一个较长的新鲜度生命周期是合理的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cache-Control: public, max-age=31536000, immutable
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但对一个内容可能被原地替换的稳定 URL，该策略不安全。如果 URL 稳定，应优先使用较短的生命周期，并搭配实体标签（&lt;code&gt;ETag&lt;/code&gt;）或有意义的 &lt;code&gt;Last-Modified&lt;/code&gt; 值。&lt;/p&gt;
&lt;h3 id="同时检查两条路径"&gt;同时检查两条路径&lt;/h3&gt;
&lt;p&gt;发一次普通请求，再发一次条件请求。各服务器的具体标志可能不同，但你想要的证据是稳定的：&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;curl -sS -D - -o /dev/null https://www.example.test/
&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;If-None-Match: &amp;#34;copy-a-real-etag-from-the-first-response&amp;#34;&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; https://www.example.test/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;不要在 runbook 里粘贴编造的验证器。从第一次响应里复制该值，或使用测试夹具。只有服务器能证明所选表示未变化时，成功的重校验才应返回 &lt;code&gt;304&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="让中间层可见"&gt;让中间层可见&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Age&lt;/code&gt;、&lt;code&gt;Vary&lt;/code&gt; 与 cache-control 指令说明了为什么两个客户端可能得到不同结果。如果响应随 &lt;code&gt;Accept-Encoding&lt;/code&gt;、语言或其他请求字段变化，该维度应进入 &lt;code&gt;Vary&lt;/code&gt;；否则共享缓存可能复用错误的表示。&lt;/p&gt;
&lt;p&gt;有意义的发布检查不是&amp;quot;扫描器找到了缓存头&amp;quot;，而是&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/rfc9111.html"&gt;RFC 9111 — HTTP Caching&lt;/a&gt;，第 4–5 节，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，第 8 与 13 节，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control"&gt;MDN: &lt;code&gt;Cache-Control&lt;/code&gt;&lt;/a&gt;，指令语义与默认值，已核验 2026-08-22。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>压缩会改变所选中的 HTTP 表示</title><link>https://www.xbcatm.com/zh/notes/compression-changes-the-selected-http-representation/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/compression-changes-the-selected-http-representation/</guid><description>把内容编码、验证器与 Vary 视为同一个可观察的响应契约。</description><content:encoded>&lt;p&gt;压缩节省传输字节，但也制造了资源的另一种表示。客户端用 &lt;code&gt;Accept-Encoding&lt;/code&gt; 声明可接受的内容编码；服务器用 &lt;code&gt;Content-Encoding&lt;/code&gt; 描述实际应用的编码。&lt;/p&gt;
&lt;h3 id="让协商可见"&gt;让协商可见&lt;/h3&gt;
&lt;p&gt;当响应可能随 &lt;code&gt;Accept-Encoding&lt;/code&gt; 变化时，把该请求字段写进 &lt;code&gt;Vary&lt;/code&gt;。这告诉共享缓存：压缩响应与 identity 响应不是可互换的缓存条目。&lt;/p&gt;
&lt;p&gt;不要只检查一个浏览器请求，而是比较两条路径：&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;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; https://www.example.test/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS --compressed -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; https://www.example.test/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;检查 &lt;code&gt;Content-Encoding&lt;/code&gt;、&lt;code&gt;Vary&lt;/code&gt;、&lt;code&gt;Content-Length&lt;/code&gt;（如存在）与验证器。服务器可为不同编码的表示使用不同实体标签；重要的是条件请求一致地验证同一个所选表示。&lt;/p&gt;
&lt;h3 id="压缩合适的内容"&gt;压缩合适的内容&lt;/h3&gt;
&lt;p&gt;HTML、CSS、JSON、XML、SVG 等文本格式通常适合压缩；已经压缩过的格式未必适合。Caddy 的 &lt;code&gt;encode&lt;/code&gt; 指令会协商支持的编码，并且通常对没有合适内容类型或体积过小的响应不做压缩。&lt;/p&gt;
&lt;p&gt;不要为了满足某个评分而加压缩。测量一个有代表性的响应，确认边缘返回预期的头，并核验可下载文件在传输解码后仍保留正确的内容类型与校验和。&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"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，内容编码与 &lt;code&gt;Vary&lt;/code&gt;，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/docs/caddyfile/directives/encode"&gt;Caddy &lt;code&gt;encode&lt;/code&gt; 指令&lt;/a&gt;，内容编码选择与协商，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://curl.se/docs/manpage.html"&gt;curl manual&lt;/a&gt;，&lt;code&gt;--compressed&lt;/code&gt;，已核验 2026-08-22。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>重定向与错误码应保留故事的完整性</title><link>https://www.xbcatm.com/zh/notes/redirects-and-error-codes-should-preserve-the-story/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/redirects-and-error-codes-should-preserve-the-story/</guid><description>用永久重定向与缺失类状态码描述发生了什么，而不是隐藏一条坏路由。</description><content:encoded>&lt;p&gt;HTTP 状态码是接口的一部分。浏览器可能渲染出一个友好的页面，但监控、爬虫、缓存与 API 客户端也需要状态码与响应头来讲述同一个故事。&lt;/p&gt;
&lt;h3 id="永久移动并不都等价"&gt;永久移动并不都等价&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;308 Permanent Redirect&lt;/code&gt; 在保留请求方法的同时，传达资源有了新的永久 URI。这对发送 &lt;code&gt;POST&lt;/code&gt;、&lt;code&gt;PUT&lt;/code&gt; 或其他非 &lt;code&gt;GET&lt;/code&gt; 方法的客户端很重要。旧页面到新页面的重定向用 &lt;code&gt;GET&lt;/code&gt; 很容易测试；端点的迁移则必须用真实调用方实际使用的方法来测试。&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;curl -sS -D - -o /dev/null --max-redirs &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; https://old.example.test/api/item
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;检查 &lt;code&gt;Location&lt;/code&gt; 是否为绝对地址或正确锚定、目标是否是预期主机，以及响应是否被静默降级为临时重定向。&lt;code&gt;308&lt;/code&gt; 默认可缓存，因此只有在有意识的迁移计划时才改动它。&lt;/p&gt;
&lt;h3 id="区分缺失与被移除"&gt;区分&amp;quot;缺失&amp;quot;与&amp;quot;被移除&amp;quot;&lt;/h3&gt;
&lt;p&gt;当服务器找不到当前表示且缺失可能是暂时或未知时，使用 &lt;code&gt;404 Not Found&lt;/code&gt;。&lt;code&gt;410 Gone&lt;/code&gt; 更强：它表示资源被有意移除且不太可能回归。两者都应有帮助人类的正文，但都不应因为自定义错误页成功渲染就返回 &lt;code&gt;200&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这条检查应放在边缘：&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;curl -sS -o /tmp/missing.html -w &lt;span class="s1"&gt;&amp;#39;%{http_code} %{url_effective}\n&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; https://www.example.com/a-route-that-does-not-exist
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在错误路径上也应保留安全响应头。安全头、有用的内容类型与正确的状态码，比一个装饰性的错误动画更有价值。&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"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，第 15 与 13 节，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7538.html"&gt;RFC 7538 — 308 Permanent Redirect&lt;/a&gt;，第 3 节，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://curl.se/docs/manpage.html"&gt;curl manual&lt;/a&gt;，状态与重定向检查标志，已核验 2026-08-22。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>