<?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>Debugging · XB 现场笔记</title><link>https://hk.xbcatm.com/zh/tags/debugging/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://hk.xbcatm.com/zh/tags/debugging/index.xml" rel="self" type="application/rss+xml"/><item><title>像客户端一样读取活动边缘</title><link>https://hk.xbcatm.com/zh/tutorials/http-edge-walkthrough/</link><pubDate>Fri, 07 Aug 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/tutorials/http-edge-walkthrough/</guid><description>用 curl 检查方法、表示元数据、范围与缓存，区分协议要求和端点的具体选择。</description><content:encoded>&lt;p&gt;HTTP 基础篇的每条笔记各取一纸契约并展示单独的证据。本教程则按顺序走完一次客户端请求触达的完整路径，只用只读命令。目标是区分协议要求与某个服务器的选择，而不是要求所有端点都返回相同状态和头部。&lt;/p&gt;
&lt;p&gt;我们全程用 &lt;code&gt;www.xbcatm.com&lt;/code&gt;。把它换成你要检查的主机。请求使用 HTTP 安全方法，但仍可能产生访问日志或消耗速率额度。只检查你有权查询的端点。下面输出块是示意，不是当前生产测量。请在一次性工作目录运行 shell 片段，保留错误，并记录时间和客户端版本。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/tutorial-http.detail.zh.svg" alt="编号 HTTP 路径区分方法策略、元数据、HEAD 对应关系、字节范围、条件复用与编码。当前成功的 200 提供实际校验器和完整正文；匹配的 304 保留该正文。状态和长度示例不是通用承诺。" width="720" height="3750"&gt;&lt;figcaption&gt;保持所选表示一致，并在各自契约内解读观察结果。本图未查询活动边缘。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="1-先确定方法可以问什么"&gt;1. 先确定方法可以问什么&lt;/h2&gt;
&lt;p&gt;在读响应体之前，先确定服务器允许你问什么。静态处理器通常支持 GET 和 HEAD，代理也可能实现 OPTIONS。先询问边缘对目标资源的处理：&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;$ curl -sS --max-time 20 -D - -o /dev/null -w &amp;#39;OPTIONS -&amp;gt; HTTP %{http_code}\n&amp;#39; -X OPTIONS https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/2 405
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;allow: GET, HEAD
&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;OPTIONS -&amp;gt; HTTP 405
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;若服务器识别 OPTIONS、但该资源不支持它，&lt;code&gt;405 Method Not Allowed&lt;/code&gt; 是合法答案，且 405 必须携带 &lt;code&gt;Allow&lt;/code&gt;。成功的 200 或 204 OPTIONS 响应也可能正确。&lt;code&gt;501&lt;/code&gt; 表示服务器未实现该方法。一次 OPTIONS 不能证明其他方法的行为。不要为了测试拒绝而向不熟悉的线上服务发送 POST：它不是安全方法，单有 200 也不能描述其副作用。&lt;/p&gt;
&lt;h2 id="2-读取表示行"&gt;2. 读取表示行&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-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -sS --max-time 20 -D - -o /dev/null https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/2 200
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;accept-ranges: bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-length: 15890
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-security-policy: default-src &amp;#39;self&amp;#39;; base-uri &amp;#39;self&amp;#39;; form-action &amp;#39;none&amp;#39;; frame-ancestors &amp;#39;none&amp;#39;; object-src &amp;#39;none&amp;#39;; script-src &amp;#39;none&amp;#39;; style-src &amp;#39;self&amp;#39;; img-src &amp;#39;self&amp;#39; data:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-type: text/html; charset=utf-8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;etag: &amp;#34;dkx84jwth7nkc9e&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;last-modified: Mon, 24 Aug 2026 13:55:23 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;strict-transport-security: max-age=31536000; includeSubDomains
&lt;/span&gt;&lt;/span&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;p&gt;三个头部值得停下来。&lt;code&gt;content-type: text/html; charset=utf-8&lt;/code&gt; 命中了媒体类型，&lt;code&gt;content-type-and-nosniff&lt;/code&gt; 说明为什么它必须描述你真正发送的字节。&lt;code&gt;etag: &amp;quot;dkx84jwth7nkc9e&amp;quot;&lt;/code&gt; 交给你强验证器。&lt;code&gt;x-content-type-options: nosniff&lt;/code&gt; 限制浏览器的 MIME 处理，尤其是脚本与样式表抓取；它不会验证正文确实是 HTML。每一个都是证据而不是装饰，合在一起是说：该响应声明了应与正文及预期策略比较的元数据。语法上为强 ETag 的值仍是服务器的声明，不是独立的内容哈希证明。&lt;/p&gt;
&lt;h2 id="3-对同一表示比较-head-与-get"&gt;3. 对同一表示比较 HEAD 与 GET&lt;/h2&gt;
&lt;p&gt;HEAD 使用 GET 的语义，但不带响应正文。服务器应返回相同头部，不过可以省略只有生成内容时才能确定的字段。&lt;code&gt;Content-Length&lt;/code&gt; 可省略；若在 HEAD 中发送，它必须等于相应 GET 会发送的内容大小。应在内容稳定时使用相同编码及其他选择头比较：&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;$ curl -sSI --max-time 20 -H &amp;#39;Accept-Encoding: identity&amp;#39; https://www.xbcatm.com/ | grep -i content-length
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-length: 15890
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -sS --max-time 20 -H &amp;#39;Accept-Encoding: identity&amp;#39; https://www.xbcatm.com/ | wc -c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;15890
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;两者一致：都是 15890。这就是 &lt;code&gt;head-request-parity&lt;/code&gt; 契约。仅缺少 HEAD 长度不是协议违规。长度不符需要调查，也要排除两次请求之间发生发布、或选中不同表示的可能。没有 &lt;code&gt;pipefail&lt;/code&gt; 的 shell 中，传给 &lt;code&gt;wc&lt;/code&gt; 的管道可能隐藏 curl 失败；自动关卡应先确认 curl 成功保存正文，再统计文件字节。&lt;/p&gt;
&lt;h2 id="4-用范围请求精确的字节"&gt;4. 用范围请求精确的字节&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-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -sS --max-time 20 -D - -o /dev/null -H &amp;#39;Accept-Encoding: identity&amp;#39; -H &amp;#39;Range: bytes=0-99&amp;#39; -w &amp;#39;HTTP %{http_code} bytes=%{size_download}\n&amp;#39; https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/2 206
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-range: bytes 0-99/15890
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-length: 100
&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;HTTP 206 bytes=100
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对于至少 100 字节的表示，成功的单范围响应应有 &lt;code&gt;206&lt;/code&gt;、匹配的 &lt;code&gt;Content-Range: bytes 0-99/…&lt;/code&gt;，以及 100 个内容字节。服务器可合法忽略 Range 并返回完整 200，这不说明它缺少校验器。无法满足的范围可能返回 416。普通 Range 请求不要求 ETag；使用 &lt;code&gt;If-Range&lt;/code&gt; 恢复特定表示，或安全拼接部分响应时，才要遵守相关强校验器规则。范围针对所选编码后的字节，因此这里显式选择 identity。&lt;/p&gt;
&lt;h2 id="5-用验证器让缓存重新验证"&gt;5. 用验证器让缓存重新验证&lt;/h2&gt;
&lt;p&gt;现在把方法、表示与字节选择通过缓存结合。新鲜的已存响应可能无需检查校验器就可复用。测试重新验证时，应抓取当前响应，并用相同表示选择重放其实际 ETag，不要复制文章里的旧标签：&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.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;work&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;mktemp -d&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="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;curl -sS --max-time &lt;span class="m"&gt;20&lt;/span&gt; -H &lt;span class="s1"&gt;&amp;#39;Accept-Encoding: identity&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; -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$work&lt;/span&gt;&lt;span class="s2"&gt;/headers&amp;#34;&lt;/span&gt; -o &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$work&lt;/span&gt;&lt;span class="s2"&gt;/body&amp;#34;&lt;/span&gt; -w &lt;span class="s1"&gt;&amp;#39;%{http_code}&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 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;$status&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;etag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;awk &lt;span class="s1"&gt;&amp;#39;tolower($1)==&amp;#34;etag:&amp;#34; {sub(/\r$/, &amp;#34;&amp;#34;); sub(/^[^:]*:[ \t]*/, &amp;#34;&amp;#34;); print; exit}&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$work&lt;/span&gt;&lt;span class="s2"&gt;/headers&amp;#34;&lt;/span&gt;&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; -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$etag&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;No ETag: inspect Last-Modified instead&amp;#39;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; 1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS --max-time &lt;span class="m"&gt;20&lt;/span&gt; -H &lt;span class="s1"&gt;&amp;#39;Accept-Encoding: identity&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; -H &lt;span class="s2"&gt;&amp;#34;If-None-Match: &lt;/span&gt;&lt;span class="nv"&gt;$etag&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -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; -w &lt;span class="s1"&gt;&amp;#39;HTTP %{http_code}\n&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;把此片段作为独立 shell 脚本运行。只有初次传输完整成功且状态为 200，才有可用的已存响应；错误表示也可能有 ETag。若所选表示仍匹配且正常处理成功，预期得到 304，并用更新后的元数据复用这份完整正文。表示变化时，200 完全可能是正确结果。检查结果期间保留临时目录，完成后仅删除此处生成的目录。&lt;code&gt;If-None-Match&lt;/code&gt; 使用弱比较，所以 304 不一定证明字节完全一致。新鲜度寿命与当前年龄决定能否直接复用；校验器回答的是另一问题，本身不设定寿命。&lt;/p&gt;
&lt;h2 id="6-协商更小的表示"&gt;6. 协商更小的表示&lt;/h2&gt;
&lt;p&gt;同样的内容在客户端表明能解压时可以传递更少字节。请求 gzip 并观察正文缩水而语义不变：&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;$ curl -sS --max-time 20 -H &amp;#39;Accept-Encoding: identity&amp;#39; -o /dev/null -w &amp;#39;no-enc: %{size_download} bytes\n&amp;#39; https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -sS --max-time 20 -o /dev/null -H &amp;#39;Accept-Encoding: gzip&amp;#39; -w &amp;#39;gzip: %{size_download} bytes\n&amp;#39; https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;no-enc: 15890 bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gzip: 4368 bytes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;示例大小展示一种可能的缩减；服务器也可以选择 identity。这是 &lt;code&gt;http-compression-negotiation&lt;/code&gt; 笔记的核心：改变选中的表示会改变线上字节，元数据（&lt;code&gt;content-length&lt;/code&gt;、&lt;code&gt;encoding&lt;/code&gt;）也随之改变。请求压缩的客户端与存储编码形式的缓存必须就选了哪种表示达成一致，否则缓存可能交付不匹配结果。除字节数外，还应检查 &lt;code&gt;Content-Encoding&lt;/code&gt; 与 &lt;code&gt;Vary: Accept-Encoding&lt;/code&gt;。手工设置 &lt;code&gt;Accept-Encoding&lt;/code&gt; 不会让 curl 自动解压；&lt;code&gt;--compressed&lt;/code&gt; 才会请求受支持编码并解码响应。应比较解码后的正文来确认内容相同，也不要假定不同编码共用同一个强 ETag。&lt;/p&gt;
&lt;h2 id="7-跟随-url-变化时的故事"&gt;7. 跟随 URL 变化时的故事&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-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -sS --max-time 20 -o /dev/null -w &amp;#39;%{http_code} -&amp;gt; %{redirect_url}\n&amp;#39; -I http://www.xbcatm.com/ | head -1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;308 -&amp;gt; https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;308 Permanent Redirect&lt;/code&gt; 保持方法与故事，正是 &lt;code&gt;redirects-and-status-codes&lt;/code&gt; 关心的。无论是永久移动的 301/308，还是由站点处理的 404，状态行都是故事的第一句，应在读正文前先读。&lt;/p&gt;
&lt;h2 id="8-最后让爬虫读地图"&gt;8. 最后让爬虫读地图&lt;/h2&gt;
&lt;p&gt;边缘也会被根本不请求 HTML 的机器读取。发现机制回答两个不同的问题，&lt;code&gt;robots-and-sitemaps&lt;/code&gt; 有说明。确认发现机制指向真实 sitemap：&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;$ curl -sS --max-time 20 https://www.xbcatm.com/robots.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;User-agent: *
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Disallow:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Sitemap: https://www.xbcatm.com/sitemap.xml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;也应抓取 sitemap URL，核对规范主机名与语言 URL。&lt;code&gt;robots.txt&lt;/code&gt; 是爬虫指引，不是访问控制；被列入 sitemap 不保证抓取或索引。&lt;/p&gt;
&lt;h2 id="所有检查一览"&gt;所有检查一览&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;检查&lt;/th&gt;
&lt;th&gt;应检查什么&lt;/th&gt;
&lt;th&gt;笔记&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OPTIONS&lt;/code&gt; 状态 + Allow&lt;/td&gt;
&lt;td&gt;对该资源观察到的方法策略&lt;/td&gt;
&lt;td&gt;http-method-semantics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;content-type&lt;/code&gt; + 正文 + &lt;code&gt;nosniff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;声明的类型与 MIME 处理策略&lt;/td&gt;
&lt;td&gt;content-type-and-nosniff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HEAD/GET 元数据&lt;/td&gt;
&lt;td&gt;对应表示，允许规定的字段省略&lt;/td&gt;
&lt;td&gt;head-parity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Range + Content-Range&lt;/td&gt;
&lt;td&gt;观察到的部分传输支持&lt;/td&gt;
&lt;td&gt;http-range-requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;抓取的 &lt;code&gt;ETag&lt;/code&gt; -&amp;gt; 304&lt;/td&gt;
&lt;td&gt;条件表示匹配&lt;/td&gt;
&lt;td&gt;conditional-requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;308 保留方法&lt;/td&gt;
&lt;td&gt;重定向保留故事&lt;/td&gt;
&lt;td&gt;redirects-and-status-codes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;robots -&amp;gt; sitemap&lt;/td&gt;
&lt;td&gt;发现答案不同&lt;/td&gt;
&lt;td&gt;robots-and-sitemaps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;每一行原来是一条笔记；现在是你可对任意边缘运行的命令。需要细节时读链接的笔记，把这个表留作再次检查时的速查。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://fetch.spec.whatwg.org/#x-content-type-options-header"&gt;WHATWG Fetch Standard&lt;/a&gt;，nosniff 处理与请求目标，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，方法、验证器与状态码，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9111.html"&gt;RFC 9111 — HTTP Caching&lt;/a&gt;，新鲜度与重新验证，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7538.html"&gt;RFC 7538 — 308 Permanent Redirect&lt;/a&gt;，保留方法的重定向，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://curl.se/docs/manpage.html"&gt;curl 手册&lt;/a&gt;，只读检查选项，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>重定向与错误码应保留故事的完整性</title><link>https://hk.xbcatm.com/zh/notes/redirects-and-error-codes-should-preserve-the-story/</link><pubDate>Tue, 14 Jul 2026 00:00:00 +0000</pubDate><guid>https://hk.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;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/redirect-map.detail.zh.svg" alt="旧端点收到 POST 后返回带 Location 的 308；解析 Location 后，客户端可向新端点发送 POST。307 与 308 保留方法，301 与 302 允许把 POST 改为 GET。另设缺失分支，404 表示暂时或未知的缺失，410 表示很可能永久不可访问；两者都不说明原因，友好错误页也须保留正确的非 200 状态。Location 可为相对引用，须以原 URI 为基准解析。308 缓存受方法与缓存控制限制，缓存可能比配置存续更久。curl 不加 --location 不会跟随。" width="720" height="2960"&gt;&lt;figcaption&gt;有意选择迁移或缺失语义。保留所需方法，解析 Location，考虑缓存，并让错误页状态与事实一致。&lt;/figcaption&gt;&lt;/figure&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 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; 时，curl 不会跟随重定向。相对原 URI 解析 &lt;code&gt;Location&lt;/code&gt;：绝对 URI、根相对路径或其他相对引用都可能有效。检查解析后的主机、路径与协议。&lt;code&gt;301&lt;/code&gt;/&lt;code&gt;302&lt;/code&gt; 允许将 POST 改为 GET；&lt;code&gt;307&lt;/code&gt;/&lt;code&gt;308&lt;/code&gt; 保留方法。改变状态的方法只能在隔离端点测试。&lt;code&gt;308&lt;/code&gt; 可以启发式缓存，但仍受方法与缓存控制限制，因此缓存的迁移结果可能在服务器配置变化后继续存在。&lt;/p&gt;
&lt;p&gt;如果目的是在完成操作后展示结果，&lt;code&gt;303 See Other&lt;/code&gt; 会指向客户端可用 GET 或 HEAD 获取的另一资源。它不是要求客户端在新地址重放原来的写入操作；应有意选择这种行为，而不是把所有重定向都当作迁移。&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;code&gt;404&lt;/code&gt; 也可以用于隐藏某个禁止访问的资源是否存在。&lt;code&gt;404&lt;/code&gt; 与 &lt;code&gt;410&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 -D missing.headers -o 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.test/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-07-14。&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-07-14。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://curl.se/docs/manpage.html"&gt;curl manual&lt;/a&gt;，状态与重定向检查标志，已核验 2026-07-14。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>用两条命令读取公共边缘</title><link>https://hk.xbcatm.com/zh/notes/read-the-public-edge-in-two-commands/</link><pubDate>Mon, 13 Jul 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/read-the-public-edge-in-two-commands/</guid><description>读取响应头与证书日期，同时区分到期、身份与字节一致性的验证。</description><content:encoded>&lt;p&gt;部署完成意味着公共响应符合发布契约，而不是最后一条上传命令退出码为零。&lt;a href="https://hk.xbcatm.com/zh/resources/"&gt;操作套件&lt;/a&gt;里的两个只读工具能提供有用证据，但不能证明整个制品都已正确发布。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/field-output.detail.zh.svg" alt="源文中的示意片段展示 HTTP_STATUS 200 与 FINAL_URL，以及完整的 2026 年证书到期时间和 30 天到期检查结果。注释区分命令成功与 HTTP 状态，以及到期与 notBefore、主机名和信任检查；正文比较与 SAN 身份策略另行验证。" width="720" height="1970"&gt;&lt;figcaption&gt;这些片段仅为示意。分别判断 HTTP 状态与到期结果；主机名、信任与正文一致性需要对应的独立检查。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="检查响应头"&gt;检查响应头&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;check-http-headers.sh&lt;/code&gt; 发出 GET、丢弃正文、跟随重定向，打印每一跳的响应头以及最终状态和 URL：&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;check-http-headers.sh https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;HTTP/2 200
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;accept-ranges: bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-security-policy: default-src &amp;#39;self&amp;#39;; base-uri &amp;#39;self&amp;#39;; form-action &amp;#39;none&amp;#39;; frame-ancestors &amp;#39;none&amp;#39;; object-src &amp;#39;none&amp;#39;; script-src &amp;#39;none&amp;#39;; style-src &amp;#39;self&amp;#39;; img-src &amp;#39;self&amp;#39; data:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-type: text/html; charset=utf-8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;last-modified: Mon, 24 Aug 2026 11:20:54 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;permissions-policy: accelerometer=(), camera=(), geolocation=(), gyroscope=(), microphone=(), payment=(), usb=()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;referrer-policy: strict-origin-when-cross-origin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;strict-transport-security: max-age=31536000; includeSubDomains
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;x-content-type-options: nosniff
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;x-frame-options: DENY
&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;HTTP_STATUS 200
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;FINAL_URL https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;核对预期状态、最终主机与路径，以及最终响应上的策略。工具没有使用 curl 的 &lt;code&gt;--fail&lt;/code&gt;：HTTP 404 或 500 仍可能使 shell 命令成功结束。必须判断它打印的状态。响应头不一致需要调查服务器、代理或缓存配置；单凭这种不一致，不能证明活动输出目录选错了。&lt;/p&gt;
&lt;h2 id="检查证书日期"&gt;检查证书日期&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;check-tls-expiry.sh HOST [DAYS]&lt;/code&gt; 提取使用 SNI 时服务器呈现的证书，并检查距离到期是否至少还有指定天数对应的秒数：&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;check-tls-expiry.sh www.xbcatm.com &lt;span class="m"&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;subject=CN=www.xbcatm.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;issuer=C=US, O=Let&amp;#39;s Encrypt, CN=YE1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;notBefore=Aug 21 08:37:13 2026 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;notAfter=Nov 19 08:37:12 2026 GMT
&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;certificate remains valid for at least 30 day(s)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;工具打印主体、签发者、日期与 SHA-256 指纹，不打印剩余天数。成功消息只描述到期检查：脚本没有强制验证主机名或信任链，&lt;code&gt;-checkend&lt;/code&gt; 也不验证 &lt;code&gt;notBefore&lt;/code&gt;。主体 CN 不足以证明主机身份；服务身份应与证书的主体备用名称匹配。有效的通配符也可能合法覆盖该主机。&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;openssl s_client -connect www.xbcatm.com:443 -servername www.xbcatm.com &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -verify_hostname www.xbcatm.com -verify_return_error &amp;lt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这使用已配置的信任库，并在 OpenSSL 匹配规则下的证书验证出错时失败。在 OpenSSL 3.0 中，没有合适的 DNS 主题备用名称时，主机名验证可能回退到主题 CN。因此，命令成功本身不能证明符合 RFC 9525 仅使用 SAN 的身份策略：还要检查 SAN 扩展，并单独验证目标客户端的策略。TLS 工具本身没有网络截止时间，应在任务层设置超时。证书检查与自动续期配置仍是两项独立检查。&lt;/p&gt;
&lt;h2 id="在发布前后使用两者"&gt;在发布前后使用两者&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;发布前：保存预期策略、最终 URL、验证器与证书日期，并注明 UTC 采集时间。&lt;/li&gt;
&lt;li&gt;部署已经验证的制品。&lt;/li&gt;
&lt;li&gt;发布后：重复探测，并下载代表性正文与该制品比较。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;响应头与证书日期不能证明字节一致。&lt;code&gt;Last-Modified&lt;/code&gt; 可能不变，也可能在复制中被重写。&lt;a href="https://hk.xbcatm.com/zh/notes/a-release-is-not-complete-until-the-public-edge-agrees/"&gt;发布冒烟测试&lt;/a&gt;保存正文并直接比较；完整清单可将检查扩展到单个页面之外。浏览器行为、不同地区边缘的差异，以及只在负载下出现的故障，需要另行测试。记录实际检查过的路径与观察点。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://curl.se/docs/manpage.html"&gt;curl manual&lt;/a&gt;，GET、重定向、响应头输出与 HTTP 失败行为，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.0/man1/openssl-s_client/"&gt;OpenSSL &lt;code&gt;s_client&lt;/code&gt;&lt;/a&gt;，SNI、显式主机名检查与验证错误时失败，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.0/man1/openssl-verification-options/"&gt;OpenSSL verification options&lt;/a&gt;，主机名匹配与主题 CN 回退，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.0/man1/openssl-x509/"&gt;OpenSSL &lt;code&gt;x509&lt;/code&gt;&lt;/a&gt;，证书显示与只检查到期的 &lt;code&gt;-checkend&lt;/code&gt; 行为，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9525.html"&gt;RFC 9525 — Service Identity in TLS&lt;/a&gt;，主体备用名称与通配符身份匹配，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/docs/automatic-https"&gt;Caddy Automatic HTTPS&lt;/a&gt;，自动证书管理与续期配置，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP"&gt;MDN: Content Security Policy guide&lt;/a&gt;，所示策略的背景说明，已核验 2026-07-13。&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;，响应元数据与验证器不能证明完整制品匹配，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>HTTP 方法是对副作用做出的承诺</title><link>https://hk.xbcatm.com/zh/notes/http-method-semantics/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/http-method-semantics/</guid><description>HTTP 方法说明了请求可能改变什么；静态源站可以在不假装有状态契约的情况下应答其中大多数方法。</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/http-methods.detail.zh.svg" alt="语义矩阵区分安全的 GET、HEAD 与不安全但幂等的 PUT、DELETE。限定的 Caddy v2.10.2 文件路径接受 GET、HEAD，以 405 和 Allow 拒绝其他方法，更早应答的处理器可能不同。安全方法仍可产生附带日志；幂等不保证状态码相同，也不意味着每次失败后都适合重试。HEAD 的 Content-Length 若提供须对应同编码 GET，省略可能合法。方法支持与 CORS 权限分开判断。" width="720" height="2900"&gt;&lt;figcaption&gt;先区分是否请求改变状态与重复请求的预期效果，再检查实际路由。文件处理器边界有明确适用范围，方法支持与 CORS 权限分开判断。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="安全与幂等回答不同的问题"&gt;安全与幂等回答不同的问题&lt;/h2&gt;
&lt;p&gt;GET 与 HEAD 是安全方法：客户端没有请求改变状态的操作。但附带的日志、指标或计费仍可能改变服务器状态。PUT 与 DELETE 按其定义具有幂等语义，却并不安全：重复相同请求的预期效果与执行一次相同。这不要求响应状态码相同，也不意味着每次网络失败后都适合重试。&lt;/p&gt;
&lt;p&gt;对只读静态源站：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GET&lt;/strong&gt; 获取表示。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HEAD&lt;/strong&gt; 描述对应的 GET，但不携带响应内容。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OPTIONS&lt;/strong&gt; 询问目标的通信选项；文件处理器可以拒绝它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;POST / PUT / DELETE / PATCH&lt;/strong&gt; 不应直接落入成功的文件响应，除非另有明确设计的处理器实现其语义。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="caddy-文件处理器的方法边界"&gt;Caddy 文件处理器的方法边界&lt;/h2&gt;
&lt;p&gt;Caddy v2.10.2 的文件服务路径接受 GET 与 HEAD，并以 &lt;code&gt;405&lt;/code&gt; 和 &lt;code&gt;Allow: GET, HEAD&lt;/code&gt; 拒绝其他方法。重定向、缺失路径、错误处理器与其他配置路由可能更早应答或采取不同处理，因此这不是每个静态网站 URL 的统一响应。&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;$ curl -s -o /dev/null -w &amp;#39;OPTIONS -&amp;gt; HTTP %{http_code}\n&amp;#39; -X OPTIONS https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;OPTIONS -&amp;gt; HTTP 405
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里没有保留原始响应日志，本轮审阅也未重现该在线结果。命令只报告状态码，不能证明必需的 &lt;code&gt;Allow&lt;/code&gt; 字段存在，需另行检查。&lt;code&gt;405&lt;/code&gt; 表示已知方法不被该目标支持；如果服务器根本不识别或未实现该方法，通常应区分为 &lt;code&gt;501 Not Implemented&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="head-一致性允许规范规定的省略"&gt;HEAD 一致性允许规范规定的省略&lt;/h2&gt;
&lt;p&gt;HEAD 应描述与 GET 相同的所选表示，同时省略内容。只有生成内容时才能确定的字段可以省略。如果存在 &lt;code&gt;Content-Length&lt;/code&gt;，它必须描述采用相同内容编码的对应 GET 内容长度。&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;HEAD content-length: 15890
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GET bytes read : 15890
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这些历史数字不是固定的期望大小，也不是当前验证结果。仅缺少 HEAD 长度是合法的，提供了错误长度才需要调查。成对探针可参考 &lt;code&gt;head-request-parity&lt;/code&gt; 笔记；可下载的 &lt;code&gt;check-http-headers.sh&lt;/code&gt; 只检视 GET 响应，并不会自动执行这项比较。&lt;/p&gt;
&lt;h2 id="拒绝意外写入同时说明真实契约"&gt;拒绝意外写入，同时说明真实契约&lt;/h2&gt;
&lt;p&gt;对已识别、但目标不允许的方法，返回 &lt;code&gt;405&lt;/code&gt;，并以 &lt;code&gt;Allow&lt;/code&gt; 列出实际支持的方法。应用路由的这个列表不一定只包含安全方法。不要为被忽略的写请求返回看似合理的 &lt;code&gt;200&lt;/code&gt; 页面。&lt;/p&gt;
&lt;p&gt;如果浏览器客户端需要 CORS 预检，应明确配置相应 OPTIONS 行为；方法支持与 CORS 权限是两项不同决策。写入拒绝测试应在获准使用的可丢弃夹具上进行，不要因为公共端点看起来静态，就假设向它发送写请求没有影响。&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;，安全/幂等方法与 HEAD、405、501 语义，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods"&gt;MDN: HTTP 请求方法&lt;/a&gt;，方法概览，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://curl.se/docs/manpage.html"&gt;curl 手册&lt;/a&gt;，方法与响应检视选项，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/caddyserver/caddy/v2.10.2/modules/caddyhttp/fileserver/staticfiles.go"&gt;Caddy v2.10.2 文件服务器源码&lt;/a&gt;，文件选取后的方法拒绝，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fetch.spec.whatwg.org/#cors-preflight-fetch"&gt;WHATWG Fetch 标准&lt;/a&gt;，CORS 预检处理，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>验证器决定缓存副本是否仍是答案</title><link>https://hk.xbcatm.com/zh/notes/http-conditional-requests/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/http-conditional-requests/</guid><description>ETag 与 Last-Modified 让缓存判断副本是否仍然有效，并在重新验证确认未变化时省下正文传输。</description><content:encoded>&lt;p&gt;缓存可以询问已存储的副本是否仍可使用，而不必每次重新下载正文。它发送验证器；匹配的条件 GET 可以收到 &lt;code&gt;304 Not Modified&lt;/code&gt;，表示变化时通常需要新的 &lt;code&gt;200&lt;/code&gt; 响应。验证是在命中时省下正文，并非每次请求都省下。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/etag-validation.detail.zh.svg" alt='缓存把弱 ETag W/"release-a" 原样放入 If-None-Match。它与源站标签 "release-a" 弱比较匹配，源站返回无正文的 304；缓存更新元数据并复用正文 A。另一种结果是源站标签 "release-b" 导致 GET 返回 200 与新正文 B。HEAD 使用相同状态判断，但不返回正文。弱匹配既不证明字节相同，也不证明新鲜度。' width="720" height="1750"&gt;&lt;figcaption&gt;标签仅为示意；假定请求原本成功。GET/HEAD 使用弱比较；HEAD 无正文。匹配既不证明字节相同，也不证明新鲜度。其他方法通常返回 412；If-Range 不能使用弱 ETag。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="验证器类型与强度是两回事"&gt;验证器类型与强度是两回事&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ETag&lt;/code&gt; 是带双引号的不透明值，可以是强标签（&lt;code&gt;&amp;quot;release-a&amp;quot;&lt;/code&gt;），也可以明确标记为弱标签（&lt;code&gt;W/&amp;quot;release-a&amp;quot;&lt;/code&gt;）。强比较要求两者都为强标签且值相等；弱比较忽略 &lt;code&gt;W/&lt;/code&gt; 标记，只比较不透明值。即使字节不同，弱标签也可以描述语义等价。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Last-Modified&lt;/code&gt; 是 HTTP 日期中精确到秒的时间戳。通常它是弱验证器，除非满足 RFC 9110 第 8.8.2.2 节的条件、可认定为强验证器。不能把所有 ETag 都归为强验证器，也不能无条件把所有时间戳归为弱验证器。&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;etag: &amp;#34;dkx84jwth7nkc9e&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;last-modified: Mon, 24 Aug 2026 13:55:23 GMT
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这些是历史值；这里未保留原始响应日志，本轮审阅也未重新检查在线结果。Caddy 文件服务器通常会设置这两个字段，但仍取决于文件元数据与配置。&lt;/p&gt;
&lt;h2 id="条件-get-使用弱比较"&gt;条件 GET 使用弱比较&lt;/h2&gt;
&lt;p&gt;把收到的 ETag 原样放进 &lt;code&gt;If-None-Match&lt;/code&gt;，保留双引号与可能存在的 &lt;code&gt;W/&lt;/code&gt;。原笔记针对上述值报告过 &lt;code&gt;HTTP 304&lt;/code&gt;，但展示的请求值没有双引号；该请求文本不符合实体标签语法，不应当作可工作的探针照抄。测试在线端点前应取得当前验证器。&lt;/p&gt;
&lt;p&gt;下面无需套接字的练习，展示对单个、已解析标签的比较；它不是完整的 HTTP 头部解析器：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;weak_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;W/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;tag&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;def&lt;/span&gt; &lt;span class="nf"&gt;strong_equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;W/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;W/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&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="n"&gt;stored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;W/&amp;#34;release-a&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#34;release-a&amp;#34;&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;assert&lt;/span&gt; &lt;span class="n"&gt;weak_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;weak_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;strong_equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;weak_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;weak_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#34;release-b&amp;#34;&amp;#39;&lt;/span&gt;&lt;span class="p"&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;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;weak match: True; strong match: False; changed tag: no match&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对原本会成功的 GET 或 HEAD，&lt;code&gt;If-None-Match&lt;/code&gt; 匹配时返回无正文的 &lt;code&gt;304&lt;/code&gt;。其他方法上的同一前置条件为假时，通常返回 &lt;code&gt;412 Precondition Failed&lt;/code&gt;。若同时存在 &lt;code&gt;If-None-Match&lt;/code&gt; 与 &lt;code&gt;If-Modified-Since&lt;/code&gt;，ETag 条件优先。通过 &lt;code&gt;If-Range&lt;/code&gt; 做字节范围验证时，不能使用弱 ETag。&lt;/p&gt;
&lt;h2 id="重新发布时不要保留误导性的验证器"&gt;重新发布时不要保留误导性的验证器&lt;/h2&gt;
&lt;p&gt;ETag 不一定是内容哈希。Caddy v2.10.2 的默认文件标签由修改时间与大小生成；替换字节时若两者都保持不变，标签也可能不变。检查实际部署版本与覆盖配置，再让发布流程更新验证器依赖的输入，或采用基于内容的版本标识。&lt;/p&gt;
&lt;p&gt;在发布清单中保留字节校验和，并测试未变化与已变化两种响应。校验和不会自动成为服务器的 ETag。&lt;code&gt;304&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"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，第 8.8 与 13 节的验证器强度及前置条件，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9111.html"&gt;RFC 9111 — HTTP Caching&lt;/a&gt;，第 4.3 节的验证与已存储响应更新，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Conditional_requests"&gt;MDN: HTTP 条件请求&lt;/a&gt;，条件请求概览，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/caddyserver/caddy/v2.10.2/modules/caddyhttp/fileserver/staticfiles.go"&gt;Caddy v2.10.2 文件服务器源码&lt;/a&gt;，&lt;code&gt;calculateEtag&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/certificate-chain/</link><pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/certificate-chain/</guid><description>把服务器发送的证书列表、客户端信任锚与请求主机名作为 TLS 验证的不同部分分别检查。</description><content:encoded>&lt;p&gt;TLS 客户端必须构建通向既有信任锚的有效证书路径，并验证请求的主机名。打印证书有助于诊断，但单靠打印不能证明这两点。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/cert-chain.detail.zh.svg" alt="服务器发送的叶证书与所需中间证书用于客户端路径构建，信任来自单独配置的信任锚。请求主机名与证书有效期还需分别检查；单靠打印或计数证书不能证明这些结果。" width="720" height="1740"&gt;&lt;figcaption&gt;服务器提供证书，信任来自客户端的信任锚。验证路径、请求主机名与有效期；打印或统计证书数量不能证明这些结果。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="叶证书不是全部"&gt;叶证书不是全部&lt;/h2&gt;
&lt;p&gt;端点首先出示&lt;em&gt;叶证书&lt;/em&gt;。其中包含公钥、有效期，以及用于主机名检查的主体备用名称。公共 CA 的部署通常还应发送构建路径所需的中间证书。缺少中间证书时，没有缓存它、也无法另行取得它的客户端可能失败。&lt;/p&gt;
&lt;p&gt;通常不必发送自签名根证书：信任来自客户端配置的信任锚，而不是服务器把某张证书放进列表。主体名称包含“Root”的交叉签名证书，在所选路径中也可能充当中间证书。因此，证书数量本身不能判断完整性或信任。&lt;/p&gt;
&lt;h2 id="阅读边缘实际发送的列表"&gt;阅读边缘实际发送的列表&lt;/h2&gt;
&lt;p&gt;OpenSSL 文档把 &lt;code&gt;s_client -showcerts&lt;/code&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;certificate 0: CN=www.xbcatm.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; issuer : C=US, O=Let&amp;#39;s Encrypt, CN=YE1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; notAfter : Nov 19 08:37:12 2026 GMT
&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;certificate 1: CN=YE1 (intermediate)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; issuer : C=US, O=ISRG, CN=Root YE
&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;certificate 2/3: Root YE / ISRG Root … (CA certificates)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;原记录统计了四个证书块。这里没有保留原始握手日志，本轮审阅也未重现在线握手；这些值应视为历史报告，而不是当前边缘证据。Let&amp;rsquo;s Encrypt 的证书链文档确实列出 YE1 与 Root YE 的交叉签名路径，因此仅凭这些名称不能判定链无效。具体在哪张证书处终止信任，取决于客户端信任库与路径构建。&lt;/p&gt;
&lt;h2 id="把签发授权单独看待"&gt;把签发授权单独看待&lt;/h2&gt;
&lt;p&gt;中间 CA 使根密钥可以保持离线，并将签发操作分离。吊销中间证书可能令依赖它的叶证书路径失效，甚至需要替换证书；并不会自动保全每张叶证书。&lt;/p&gt;
&lt;p&gt;CAA 在签发时授权 CA 文档规定的签发者域标识。它不固定某张中间证书，不改变浏览器信任，也不能证明已有证书符合历史 CAA 策略。&lt;code&gt;dns-caa-records&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-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ echo | openssl s_client -connect www.xbcatm.com:443 -servername www.xbcatm.com -showcerts 2&amp;gt;/dev/null | grep &amp;#39;BEGIN CERT&amp;#39; | wc -l
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它产生只读网络流量，但隐藏诊断信息，且即使前面的命令失败，最后的计数仍可能成功。不要把 &lt;code&gt;4&lt;/code&gt; 或其他固定数量设为健康阈值。只有叶证书时，应检查预期签发路径，而不是认定所有单证书链都无效。&lt;/p&gt;
&lt;p&gt;实际验证时，应使用 OpenSSL 的 &lt;code&gt;-verify_hostname&lt;/code&gt; 与 &lt;code&gt;-verify_return_error&lt;/code&gt; 选项，要求检查主机名并在验证错误时失败，保留诊断信息，同时记录信任库与客户端版本。还要测试实际支持的客户端；一套 OpenSSL 信任库不能证明所有浏览器都兼容。&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/rfc8446.html"&gt;RFC 8446 — TLS 1.3&lt;/a&gt;，第 4.4.2 节的证书列表顺序与可省略信任锚，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc5280.html"&gt;RFC 5280 — X.509 PKI&lt;/a&gt;，第 6 节的信任锚输入与路径验证，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://letsencrypt.org/docs/faq/"&gt;Let&amp;rsquo;s Encrypt FAQ&lt;/a&gt;，证书兼容性概览，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://letsencrypt.org/certificates/"&gt;Let&amp;rsquo;s Encrypt: Chains of Trust&lt;/a&gt;，YE1 与 Root YE 的交叉签名路径，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.0/man1/openssl-s_client/"&gt;OpenSSL &lt;code&gt;s_client&lt;/code&gt;&lt;/a&gt;，呈现列表与验证选项，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8659.html"&gt;RFC 8659 — CAA&lt;/a&gt;，签发授权与客户端信任的区别，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>