使用mod_rewrite映射URL至子目录PHP文件时遇无限循环问题求助
我希望通过.htaccess中的mod_rewrite将"简洁URL"映射到子目录中对应的PHP文件,例如:https://example.com/test/
映射到https://example.com/p/test.php
我的现有规则步骤如下:
- 为所有缺少末尾斜杠的URL添加斜杠:
RewriteEngine on RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_URI} !(.*)/$ RewriteRule ^(.*[^/])$ $1/ [L,R=301]
- 添加规则确保请求的路径/文件本身不存在:
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f
- 最后添加RewriteRule将请求路径映射到子目录/p/中的对应文件:
RewriteRule ^(.+)$ /p/$1\.php
这套规则在请求存在的URL时正常工作,但请求不存在的URL时会触发无限循环,生成类似这样的URL:https://example.com/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/test/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/
我曾尝试在RewriteRule前添加另一条RewriteCond来确保子目录/p/中存在对应的文件,但该方法无效:
RewriteCond %{DOCUMENT_ROOT}/p/$1.php -f
请问我哪里操作出错了?
无限循环的根源:当请求不存在的URL时,比如
https://example.com/nonexistent/,规则会将其重写到/p/nonexistent.php,但这个文件同样不存在。此时Apache会再次执行mod_rewrite规则,重写后的路径/p/nonexistent.php会被第二条规则的!-f条件匹配,接着RewriteRule会把p/nonexistent.php作为(.+)捕获,重写成/p/p/nonexistent.php.php,以此类推形成无限循环。文件存在判断失效的原因:你在RewriteCond中使用的
$1,引用的是之前RewriteCond的捕获组,而非后续RewriteRule里的捕获组。这就导致%{DOCUMENT_ROOT}/p/$1.php中的$1不是你期望的请求路径部分,条件判断自然失效。
修正规则,解决循环问题,同时正确判断目标文件是否存在:
RewriteEngine On RewriteBase / # 1. 给非文件类URL添加末尾斜杠 RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*[^/])$ $1/ [L,R=301] # 2. 仅当目标PHP文件存在时,映射简洁URL到/p/子目录 RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f # 处理请求URI,去掉末尾斜杠后拼接.php,判断文件是否存在 RewriteCond %{DOCUMENT_ROOT}/p/%{REQUEST_URI}s.php -f RewriteRule ^(.+)/$ /p/$1.php [L]
关键修正说明
- 避免循环:RewriteRule的正则改为
^(.+)/$,只匹配末尾带斜杠的原始请求,不会匹配重写后的/p/test.php这类路径;同时加上[L]标记,匹配后立即停止后续规则处理。 - 正确判断文件存在:用
%{REQUEST_URI}s处理请求路径——比如请求/test/时,%{REQUEST_URI}是/test/,加上s后会自动去掉末尾的斜杠变成/tests,拼接.php后就是/p/test.php,再结合%{DOCUMENT_ROOT}就能准确判断文件是否存在。
如果不想依赖%{REQUEST_URI}的字符串处理,也可以用捕获组引用的方式:
RewriteEngine On RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*[^/])$ $1/ [L,R=301] RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f # 捕获请求路径(去掉末尾斜杠) RewriteCond ^(.+)/$ ^(.+)/$ RewriteCond %{DOCUMENT_ROOT}/p/%1.php -f RewriteRule ^(.+)/$ /p/$1.php [L]
内容的提问来源于stack exchange,提问作者Alexander

