观察、决策、行动三步示意图,旁边是压缩属于内容协商的英文标题。

压缩协商本质上仍是内容协商

HTTP 压缩选择的是施加了内容编码的表示,不是一种新的媒体类型。经过 gzip 的文本,解压后仍然是文本。因此有效验证需要回答三个问题:编码是否可接受、传输内容是否变小、解码后是否恢复了原本要提供的字节。配置里勾选了压缩,或者接口返回成功状态,都不能同时证明这三件事。 区分缺失、排除与偏好 根据 RFC 9110,Accept-Encoding 缺失时,任何内容编码都被认为可接受;显式空值则表示不希望使用内容编码。identity 代表没有内容编码,通常默认可接受,除非被 identity;q=0 或适用的 *;q=0 排除。通配符还可以让没有逐个列出的编码成为可接受候选,因此“只能发送客户端单独点名的编码”并不是完整规则。 质量值为零表示不可接受,正数则用于表达可接受编码之间的偏好。但服务器还必须具有对应编码器,并判断内容是否适合压缩。客户端偏好 Brotli,不会替服务器安装 Brotli。如果 identity 仍然可接受,未编码响应可能就是合理结果。即使所有候选都被排除,也不能假定每个服务器都会实施严格的 406 策略,因为 RFC 9110 还允许忽略协商偏好。应用需要哪种行为,必须单独验证。 坏掉的样子 代理压缩正文却漏写 Content-Encoding,客户端就可能把压缩字节当成文本;反过来,代理解压后保留原字段,则可能诱发第二次解码。Content-Length 如果存在,描述的是编码后内容的字节数,不是原始长度。流式压缩不一定提前知道最终大小,可以不发送这个字段,而不是继续沿用旧长度。消息分帧与内容编码是不同层次,不能混为一谈。 当 Accept-Encoding 参与可复用变体的选择时,Vary 需要描述这个维度,未编码响应也不能漏掉。否则,缓存中的 gzip 副本可能送给明确排除了 gzip 的客户端。缓存新鲜度和存储资格仍是另外的问题。即使选择正确,也不保证压缩值得:很小的正文存在额外开销,已经压缩过的媒体通常收益有限。应测代表性内容和处理成本,而不是声称所有文本都有一个固定的压缩百分比。 快速验证 准备 Python 3.11 或更新版本、支持 gzip 的 curl,以及 PATH 中的 Caddy 2.11.4。从官方发布页取得适合系统的二进制,并核对公布的校验和后再运行。程序使用回环端口 18765;若该端口占用,应在代码里换成空闲端口。它关闭管理端点、自动 HTTPS 和配置持久化,只清理自己创建的临时文件与子进程,不接触正在运行的网站服务。 ...

七月 21, 2026 · 3 分钟 · XB Field Notes
观察、决策、行动三个面板,旁边是关于缓存头的双向约定的标题。

缓存头是和浏览器的一份双向契约

源站定义存储和新鲜度策略,缓存判断已保存的响应是否能满足下一次请求。无法直接复用时,验证器可以降低检查成本。这是几个相互独立的决定:在一次成功响应中找到 Cache-Control 和 ETag,并不能证明下一次请求会按预期执行。验收必须沿着重复请求继续观察,才能区分直接命中、成功验证和完整替换。 允许谁复用、可复用多久 private 把存储范围限制到单用户缓存,public 在其他条件允许时明确授予缓存许可。max-age 提供新鲜度寿命,s-maxage 则为共享缓存覆盖这个值。no-cache 允许保存,但要求每次复用前成功验证;no-store 禁止 HTTP 缓存存储,并不管住所有应用日志或用户保存的文件。这些指令都不能替代授权检查和传输安全,不能因为缓存头正确就省略访问控制。 对于带内容指纹的资源,如果字节变化一定带来新网址,public, max-age=31536000, immutable 可以是合理策略。这里的时长是一年,不是一个月。稳定网址下会变化的文档,应按更新要求选择策略:短寿命搭配验证器,允许有界的直接复用;no-cache 则要求后续每次 HTTP 缓存复用都先验证。两者的网络请求成本和可见更新延迟并不一样。 验证器回答另一个问题 客户端可以在 If-None-Match 中带回 ETag。条件 GET 的标签匹配时得到不含正文的 304;标签不匹配时,通常得到包含当前表示的 200。If-None-Match 使用弱比较,因此弱标签同样可以用于缓存验证。Last-Modified 支持 If-Modified-Since,但秒级时间戳可能遗漏快速连续变化。请求中只要存在 If-None-Match,就忽略 If-Modified-Since,而不是等标签匹配失败后再用日期兜底。 收到适用的 304 后,缓存保留原有正文,并依照缓存规则更新元数据。对于对应的 200 本来会发送的 Date、ETag、Cache-Control 等字段,源站在 304 中也有相应发送要求。一个只负责下载响应的程序,不会因此自动具备完整缓存能力;如果把 304 的空内容直接当作新的文档覆盖旧正文,反而会丢掉真正要使用的内容。 ...

七月 15, 2026 · 2 分钟 · XB Field Notes
一个旧 URI 以 308 状态永久重定向到新 URI。

重定向与错误码应保留故事的完整性

在选择重定向或错误状态前,先检查方法、Location 头与缓存行为。

七月 14, 2026 · 1 分钟 · XB Field Notes
一段很长的字节序列,其中选出了一个有界的片段用于部分响应。

范围请求应证明选中了哪些字节

只有当 206 响应的边界与总大小描述了实际返回的字节时才有意义。

七月 6, 2026 · 1 分钟 · XB Field Notes
一行请求方法,对照静态源站被允许承诺的副作用。

HTTP 方法是对副作用做出的承诺

一次把方法边界搞清楚,OPTIONS、HEAD 与幂等写入就不再是意外。

七月 2, 2026 · 1 分钟 · XB Field Notes
缓存与源站交换一个验证器字符串,以判断存储的响应是否仍然新鲜。

验证器决定缓存副本是否仍是答案

条件请求是缓存的第二个时钟;304 意味着存储的副本仍是答案。

六月 30, 2026 · 1 分钟 · XB Field Notes
一个较大的 HTTP 表示被压缩成更小的传输。

压缩会改变所选中的 HTTP 表示

分开测试压缩与未压缩请求,让缓存和验证器描述实际送达的字节。

六月 29, 2026 · 1 分钟 · XB Field Notes
缓存与源站在一枚新鲜度时钟下方交换验证器。

缓存头是两套时钟的契约

把浏览器端新鲜度与源站校验分开,再验证实现两者的响应头。

六月 25, 2026 · 1 分钟 · XB Field Notes