Apache .htaccess移除.php后缀:第二组规则无限循环原因解析
Hey there, let's dig into why your second rewrite rule is causing an infinite loop, and break down the mod_rewrite logic that's behind this behavior.
First: How mod_rewrite Actually Works Under the Hood
Before jumping into your rules, it's critical to understand the core flow of Apache's mod_rewrite:
- When a request hits your server, mod_rewrite runs through your rules in order, first checking any
RewriteCondconditions attached to a rule. If all conditions pass, it executes theRewriteRule. - The
[L]flag (short for "Last") stops processing the rest of the rules in the current set, but here's the gotcha: Apache will re-run the entire rule set against the rewritten URL. This is how internal rewrites work, but it's also the main cause of infinite loops. - The
[END]flag (available in Apache 2.4+) is different: it completely stops all rewrite processing, no more loops. - Some variables like
THE_REQUESTare immutable—they hold the original HTTP request line (e.g.,GET /test.php HTTP/1.1) and never change during rewrite processing. Variables likeREQUEST_URIorREQUEST_FILENAME, though, are mutable: they update to match the rewritten URL each time.
Why Your Second Rule Causes an Infinite Loop
Let's assume your second rule set looks something like this (a common problematic setup for hiding .php extensions):
# Problematic redirect rule RewriteCond %{REQUEST_URI} \.php$ RewriteRule ^(.*)\.php$ /$1 [R=301,L] # Internal rewrite rule RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME}\.php -f RewriteRule ^(.*)$ $1.php [L]
Here's the loop breakdown:
- A user visits
https://test.com/test.php.REQUEST_URIis/test.php, so the redirect rule triggers, sending a 301 tohttps://test.com/test. - The browser follows the redirect and requests
https://test.com/test. The internal rewrite rule kicks in, rewriting the URL to/test.phpso the server can load the actual PHP file. - Now,
REQUEST_URIis updated to/test.php—and mod_rewrite runs the entire rule set again. The redirect rule triggers once more, sending another 301 to/test. - This cycle repeats forever because the rewritten URL keeps matching the redirect condition.
The root issue here is using a mutable variable (REQUEST_URI) to check for the .php suffix. After the internal rewrite, REQUEST_URI reverts to the .php version, so the redirect rule fires again.
Why Your First Rule Works
Your first rule set likely uses the immutable THE_REQUEST variable instead of REQUEST_URI for the redirect check, like this:
# Working redirect rule RewriteCond %{THE_REQUEST} \s/+(.*?)\.php[\s?] RewriteRule ^ /%1 [R=301,L] # Internal rewrite rule RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME}\.php -f RewriteRule ^(.*)$ $1.php [L]
- When the user first visits
/test.php,THE_REQUESTisGET /test.php HTTP/1.1, which matches the condition, so the redirect fires. - When the browser requests
/test,THE_REQUESTisGET /test HTTP/1.1—no.phpin sight, so the redirect rule doesn't trigger. Only the internal rewrite runs, and there's no loop because the mutable variables don't trigger the redirect again.
Alternatively, your first rule might use the [END] flag instead of [L] for the internal rewrite, which stops all further processing after the rewrite, preventing the rule set from re-running.
内容的提问来源于stack exchange,提问作者user4447799

