<?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>Web-Performance · XB 现场笔记</title><link>https://hk.xbcatm.com/zh/tags/web-performance/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://hk.xbcatm.com/zh/tags/web-performance/index.xml" rel="self" type="application/rss+xml"/><item><title>压缩会改变所选中的 HTTP 表示</title><link>https://hk.xbcatm.com/zh/notes/compression-changes-the-selected-http-representation/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://hk.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;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/compression.detail.zh.svg" alt="协商分支选择 identity 字节 A 或编码后字节 B，因此强标签不同。Vary 控制匹配，弱标签有语义等价的条件。另一路径说明响应头保留编码后 Content-Length，curl 解码正文后才与制品校验和比较。" width="720" height="2320"&gt;&lt;figcaption&gt;压缩选择的是表示字节，协商、缓存匹配与验证器必须一致。编码后字节不同就需要不同强标签。下载正文解码后再与制品比较；编码后 Content-Length 不必等于解码文件大小。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="让协商可见"&gt;让协商可见&lt;/h2&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;h2 id="压缩合适的内容"&gt;压缩合适的内容&lt;/h2&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;p&gt;使用 &lt;code&gt;--compressed&lt;/code&gt; 时，curl 自动解码正文，但保存的响应头不会随之改写。若存在 &lt;code&gt;Content-Length&lt;/code&gt;，它描述的是编码后内容，未必等于解码后下载文件的大小。应在内容解码后才把校验和与制品比较；HTTP 传输分帧属于另一层。此外，该选项不保证服务器一定压缩，应查看真实响应，尤其是小文件与已经压缩过的文件。&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-10-15。&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-10-15。&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-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>缓存头是两套时钟的契约</title><link>https://hk.xbcatm.com/zh/notes/cache-headers-are-a-contract-with-two-clocks/</link><pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/cache-headers-are-a-contract-with-two-clocks/</guid><description>根据表示内容可能变化的频率，选择新鲜度与重校验规则。</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/cache-clocks.detail.zh.svg" alt="存储与复用矩阵之后，分别说明稳定 URL 与带指纹 URL 的策略。条件 GET 在 If-None-Match 中发送真实 ETag：弱匹配可返回 304，验证器变化时通常返回新的 200。语义等价不保证字节一致。Age、Vary、仅在新鲜期内生效的 immutable 与既有缓存寿命，共同约束复用。" width="720" height="2800"&gt;&lt;figcaption&gt;存储、新鲜度与校验是不同问题。根据 URL 的变更方式选择策略，完整保留验证器，并检查共享缓存的变体。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="先选变更模型"&gt;先选变更模型&lt;/h2&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;p&gt;除了新鲜期，还要决定谁可以存储响应。如果共享缓存不能保存面向特定用户的内容，应使用不带参数的 &lt;code&gt;private&lt;/code&gt; 指令；浏览器的私有缓存仍可存储。单独使用 &lt;code&gt;no-cache&lt;/code&gt; 不提供这项限制。上面的 &lt;code&gt;public&lt;/code&gt; 示例适用于可以在用户之间共享的资源，不是个性化响应的默认策略。&lt;/p&gt;
&lt;h2 id="同时检查两条路径"&gt;同时检查两条路径&lt;/h2&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 里粘贴编造的验证器。从第一次响应里复制该值，或使用测试夹具。对这里的条件 GET，&lt;code&gt;If-None-Match&lt;/code&gt; 匹配时可以通过弱比较返回 &lt;code&gt;304&lt;/code&gt;：它允许语义等价，并不一定保证字节完全相同。验证器变化时通常返回新的 &lt;code&gt;200&lt;/code&gt; 响应。保留 ETag 的双引号与可能存在的 &lt;code&gt;W/&lt;/code&gt; 前缀。&lt;/p&gt;
&lt;h2 id="让中间层可见"&gt;让中间层可见&lt;/h2&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;p&gt;&lt;code&gt;immutable&lt;/code&gt; 只在声明的新鲜期内生效，并非永久禁止重校验。版本化 URL 的内容应保持不变，只要旧 HTML 仍可能引用旧资源，就应让它们继续可访问。今天修改响应头不会追溯缩短既有缓存响应的寿命；缓存策略出错时，可能需要新 URL 或缓存系统明确支持的清除操作。&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-10-15。&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-10-15。&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-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8246.html"&gt;RFC 8246 — 不可变响应&lt;/a&gt;，immutable 仅在新鲜期内生效，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>