今天在调试一个其他问题时碰巧发现了一个奇怪的情况:根据我的 RewriteRule ,明明应该被 rewrite 到静态 HTML 缓存的请求,却在返回的 HTTP 头里看到了 PHP !这个 rewrite 规则应该是经过测试的,怎么突然就失效了?

第一怀疑对象是自己手贱。毕竟是个人 VPS 上的 Apache 配置,也没有使用版本控制之类的,不能确定是不是改过什么。就排查了一下。结果发现 RewriteLogLevel 这个配置竟然无效了?一查才发现,原来 Apache 2.4 里把 rewrite 日志合并到了 error 中,只需要在错误日志里加上 rewrite:traceN 就行了(其中 N 是错误级别,4足够,8应该是最详细的)。盯着错误日志看了半天,发现一个“有趣”的现象——明明已经正确匹配、 rewrite 到了静态文件,却又接着发起了 sub request ,请求了 index.php ?!

为了更好说明问题,我把我的配置简化了一下,大概是这样:

1
2
3
4
RewriteEngine on
RewriteBase /
RewriteCond %{DOCUMENT_ROOT}/cache/$1/static.html -f
RewriteRule ^(.*) "/cache/$1/static.html" [L]

简单来说就是判断一下请求的文件是不是在 cache 文件夹里有静态版本。这个简化的规则也许有一些 edge case 没有处理好,但不妨碍我们说明问题。

日志显示的情况是:

  1. 访问 http://mysite/
  2. 判断 /cache//static.html (等价于 /cache/static.html )存在。
  3. rewrite 生效,转为请求 /cache//static.html
  4. 然而,接下来又开始了一系列 sub request ,请求 index.htmlindex.phpindex.cgi 之类。很明显,这是 mod_dir 模块的 DirectoryIndex 在生效。
  5. 根目录下本来就有 index.php ,于是被 serve 了……

请求就这样结束了,“静态缓存”完全没有也不可能起效。可是,这个配置我印象深刻,简化版则更是简化到了不能更简的地步, rewrite 显然就是这么用的。我不禁怀疑—— Apache 2.4 修改了这么多,带来了巨大的兼容性问题,莫非这也是其中之一?

果然,我找到了这两个 bug :

基本是同一个问题。简单来说就是 DirectoryIndex 现在总是会起效,不论 URL 是否已经被 mod_rewrite 在内的其他 handler 修改过。值得注意的是第二个 issue ,有补丁被接受了,目前(2015.1.15)在 trunk 里,应该会和 2.4.8 一起发布。果然,在 2.4.8 的 changelog 里看到了这个:

*) mod_dir: Add DirectoryCheckHandler to allow a 2.2-like behavior, skipping execution when a handler is already set. PR53929. [Eric Covener]

然后最新版的文档里新增了这个配置项: DirectoryCheckHandler 。坑爹的是,2.4之前的版本可以看作这个配置是 on ,而2.4开始默认变成了 off 。想要关掉?必须得 2.4.8 才行!现在还没正式发布呢!