我有一个重写规则,在大多数情况下都有效。但是,如果我请求一个带有不存在的子目录的页面,并且该子目录与现有文件匹配,则会导致无限递归。
Options +FollowSymLinks
AllowOverride None
Require all granted
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.php -f
RewriteRule ^(.*)$ $1.php [L]
如果我要求,这将有效:
test.php
test
(它提供 test.php 服务)badname.php
或badname
(正确给出 404)directory/file_that_exists
或者directory/file_that_exists.php
一切正常。但如果test.php
存在,并且我请求test/test
,则会导致无限递归。错误日志如下所示:
[Wed May 23 08:16:28.176564 2018] [core:debug] [pid 27054] core.c(3620): [client 0.0.0.0:57764] AH00122: redirected from r->uri = /test/test.php.php.php
[Wed May 23 08:16:28.176566 2018] [core:debug] [pid 27054] core.c(3620): [client 0.0.0.0:57764] AH00122: redirected from r->uri = /test/test.php.php
[Wed May 23 08:16:28.176568 2018] [core:debug] [pid 27054] core.c(3620): [client 0.0.0.0:57764] AH00122: redirected from r->uri = /test/test.php
[Wed May 23 08:16:28.176570 2018] [core:debug] [pid 27054] core.c(3620): [client 0.0.0.0:57764] AH00122: redirected from r->uri = /test/test
我做错了什么?当它发现test/test.php
不存在时,它不应该失败吗?(RewriteCond %{REQUEST_FILENAME}\.php -f
)如果我只是附加一个斜线,我会得到相同的结果,例如test/
。
答案1
test/test.php
当它发现不存在时,它不应该失败吗?(RewriteCond %{REQUEST_FILENAME}\.php -f
)如果我只是添加一个斜线,我会得到相同的结果,例如test/
如果执行的是这样的检查,它将会失败,但事实并非如此。
服务器REQUEST_FILENAME
变量包含文件系统路径后URL 已映射到文件系统。这不一定与RewriteRule
图案對抗。
例如,在您的例子中,当请求/test/test.php
没有名为 的物理子目录时/test
,REQUEST_FILENAME
服务器变量包含以下形式的字符串/absolute/filesystem/path/to/test
(即,请求的 URL 直到最后一个已知目录 + 第一个路径段/test
)。并且由 匹配的 URL 路径RewriteRule
图案将是/test/test.php
(您将反复附加它,.php
因为/test.php
确实存在,如前面的RewriteCond
指令所检查的那样)。
您可以通过检查 URL 路径(构建一个绝对文件系统路径来检查)来解决这个问题,就像 @shanew 建议的那样。例如:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}/$1\.php -f
RewriteRule ^(.*)$ $1.php [L]
为了避免对每个请求进行所有这些文件系统检查,一种优化方法是首先检查扩展名.php
(和.css
、.js
等),然后再检查文件是否存在:
RewriteCond %{REQUEST_URI} !\.(php|css|js|jpg|png)$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}/$1\.php -f
RewriteRule ^(.*)$ $1.php [L]
当请求的 URL 已经以 、 等结尾时,这可以避免不必要的.php
检查.css
。
答案2
如果你在目录部分或 .htaccess 文件中执行此操作,则应该能够通过在 RewriteRule 中添加 END 选项来停止无限递归,以便它看起来像这样
重写规则 ^(.*)$ $1.php [L,END]
这将停止重写引擎执行任何后续的重写处理(而 L 仅停止当前一轮的重写处理)。Apache 2.4 重写标志文档解释得更详细。
话虽如此,我不确定这是否能让你得到你真正想要的行为,因为它仍然会将 /test/test 更改为 /test/test.php,尽管它不存在。为了解决这个问题,我认为你需要测试整个文件路径,而不仅仅是文件的名称。为此,请更改:
%{请求文件名}
进入
%{DOCUMENT_ROOT}%{REQUEST_URI}
此外,为了安全起见,我会在顶部添加另一个 RewriteCond,这样如果文件名已经以 .php 结尾,规则就不会触发(将其放在第一位意味着如果它返回 true,则可以跳过其他条件):
%{REQUEST_FILENAME} "!\.php$"