Caddy try_files 读错含 ? 或 % 的文件名
含问号或百分号的文件直读正确,经过try_files却读错时,核对URI重写和路径解码。采用正式转义处理版本,保留应用回退并验证文件内容身份。
磁盘上有 ?.html 和字面名称 %3F.html。正确编码的请求在直接使用 file_server 时读取了目标文件,加入 try_files 后却读到首页或另一个文件。此时先比较重写前后的路径,不能只看“文件明明存在”就增加权限或改 MIME 类型。
这类现象来自路径替换过程中对特殊字符的处理。正式 Caddy 2.11.7 已对相关路径占位符使用转义后内容,防止字面 ? 被当成查询分隔符、字面 % 再次被解码。无法仅凭旧配置或补丁合入时间确定所有版本的表现。
URL 数据与查询分隔符先分清
以下请求指向不同数据:
| 请求中的路径 | 对应的字面文件名 |
|---|---|
/%3F.html |
?.html |
/%253F.html |
%3F.html |
/%25.html |
%.html |
裸写 /?.html 时,问号已经分隔查询部分,不能用它测试名为 ?.html 的文件。引号只防止 shell 拆分参数,不改变 URL 的语义。
服务日志有的记录原始目标,有的记录已解析路径。保留二者对应关系,以及实际响应正文中的文件标记;状态码为 200 不代表取到的是正确文件。
在受控文件目录比较是否经过 rewrite
用允许测试的文件目录准备独立标记,保持 root、身份和其他处理一致,对照直接 file_server 与有 try_files 的路径。不要把生产目录另开一个无鉴权端口来做比较,也不要公开真实敏感文件。
发生差异的配置可能只多了这一条:
try_files {path} =404
这并非“不改路径”。try_files 先匹配文件,再把 URI 重写为匹配路径;即使文件已经存在,重写过程也会经过路径占位符处理。=404 负责未命中的结果,不会修正已命中路径的转义。
特别检查字面 %3F.html 有没有误读成 ?.html,以及字面问号有没有变成查询部分。普通 index.html 正常,只能说明普通名称路径正常。确实不存在的文件也要保留原来的失败行为,避免回退规则把所有结果掩盖成首页。
修正实现,保留应用回退契约
记录实际运行版本和有效配置,保存原二进制、插件与配置。兼容条件允许时,采用包含路径占位符转义处理的正式版本,例如 2.11.7,再用原来的文件集合复查。
新版在解析重写 URI 前保留路径的转义形式,并对匹配到的文件路径进行处理,使字面问号和百分号不被误当成新的 URI 语法。它不表示外部 URL 可以改用裸特殊字符,也不保证自定义插件中任意字符串替换都获得同样保护。
纯静态路由如果不需要 try_files 的候选和应用回退,可以审查是否移除这项多余重写。PHP 或单页应用则通常依赖回退入口,不能为了某些文件名删掉整站回退。只对确有问题的路径调整,并保留鉴权、根目录和正常错误状态。
不要批量把公网 URL 再编码一次、重命名所有文件,或将 404 改成 200。双重编码会请求另一种字面名称,客户端、中间代理和应用也可能各自进行解析;未经映射审查的统一改写会产生更多错配。
验收看内容身份,不能只看请求成功
修改后用同一客户端请求表中的三个名称,以及普通文件、目录首页、确实不存在的文件和既有应用路由。比较实际返回标记,确认字面 %3F.html 与 ?.html 仍是不同文件,查询参数也按原设计处理。
校验与加载采用该实例原有管理方式,配置通过之后仍要做上述请求检查。上游 CDN 或其他代理存在时,分别核对它们是否改变路径;源站正确不代表公网所有层都保持同一含义。
回退时一起恢复此次修改的版本和配套配置,保留原目录与文件,不用清缓存或删除文件来覆盖尚未查清的路径问题。


