为什么这会导致无限的请求循环?

今天早些时候,我正在帮助一个使用
.htaccess
用例的人,并提出了一个有效的解决方案,但我自己无法弄明白! 他希望能够: 浏览到
index.php?id=3&cat=5
请参阅位置栏
index/3/5/
内容来自
index.php?id=3&cat=5
最后两个步骤是相当典型的(通常来自用户首先输入
index/3/5
),但第一步是必需的,因为他的网站中仍然有一些旧格式的链接,无论出于何种原因,都无法更改它们。所以他需要支持这两种URL格式,让用户总是看到美化的格式。 经过多次考验,我们提出了以下
.htaccess
文件:
RewriteEngine on

# Prevents browser looping, which does seem
#   to occur in some specific scenarios. Can't
#   explain the mechanics of this problem in
#   detail, but there we go.
RewriteCond %{ENV:REDIRECT_STATUS} 200
RewriteRule .* - [L]

# Hard-rewrite ("[R]") to "friendly" URL.
# Needs RewriteCond to match original querystring.
# Uses "?" in target to remove original querystring,
#   and "%n" backrefs to move its components.
# Target must be a full path as it's a hard-rewrite.
RewriteCond %{QUERY_STRING} ^id=(d+)&cat=(d+)$
RewriteRule ^index.php$ http://example.com/index/%1/%2/? [L,R]

# Soft-rewrite from "friendly" URL to "real" URL.
# Transparent to browser.
RewriteRule ^index/(d+)/(d+)/$ /index.php?id=$1&cat=$2
虽然它似乎是一个有点奇怪的用例(“为什么不首先使用正确的链接?”,你可能会问),只是顺其自然。无论原始要求如何,这都是场景,这让我很生气。 没有第一条规则,客户端进入请求循环,重复尝试
GET /index/X/Y/
,每次获得
302
。检查
REDIRECT_STATUS
使一切顺利进行。但是我会想到,在最终规则之后,不再提供规则,客户不会再提出任何要求(注意,没有
[R]
),一切都会变成肉汁。 那么......当我拿出第一条规则时,为什么这会导致请求循环?     
已邀请:
无法修改你的设置,我不能肯定地说,但我相信这个问题是由于mod_rewrite的以下相对神秘的功能:   当您在每个目录上下文中操作URL /文件名时,mod_rewrite首先将文件名重写回其相应的URL(这通常是不可能的,但请参阅下面的RewriteBase指令以获取实现此目的的技巧),然后启动新的内部子请求使用新网址。这将重新开始API阶段的处理。 (来源:mod_rewrite技术文档,我强烈推荐阅读本文) 换句话说,当您在
.htaccess
文件中使用
RewriteRule
时,新的重写URL可能会映射到文件系统上完全不同的目录,在这种情况下,原始目录中的
.htaccess
文件将不再适用。因此,只要
.htaccess
文件中的
RewriteRule
与请求匹配,Apache就必须使用修改后的URL从头开始重新开始处理。这意味着,除其他外,每个
RewriteRule
再次被检查。 在您的情况下,会发生什么是您从浏览器访问
/index/X/Y/
。你的
.htaccess
文件中的最后一条规则会触发,重写为
/index.php?id=X&cat=Y
,因此Apache必须使用URL
/index.php?id=X&cat=Y
创建一个新的内部子请求。这符合您之前的外部重定向规则,因此Apache将302响应发送回浏览器以将其重定向到
/index/X/Y/
。但请记住,浏览器从未见过内部子请求;据他所知,它已经在
/index/X/Y/
。所以它看起来好像你被重定向到
/index/X/Y/
到同一个URL,触发无限循环。 除了性能损失之外,这可能是您应该尽可能避免在
.htaccess
文件中添加重写规则的更好理由之一。如果将这些规则移动到主服务器配置,则不会出现此问题,因为规则上的匹配不会触发内部子请求。如果您无法访问主服务器配置文件,可以通过一种方式解决它(编辑:或者我认为,虽然它似乎不起作用 - 请参阅注释)是添加
[NS]
(无子请求)标记到外部重定向规则,
RewriteRule ^index.php$ http://example.com/index/%1/%2/? [L,R,NS]
一旦你这样做,你就不再需要第一个检查
REDIRECT_STATUS
的规则了。     
下面的解决方案对我有用。
RewriteEngine on
RewriteBase /

#rule1
#Guard condition: only if the original client request was for index.php
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9} /index.php [NC]
RewriteCond %{QUERY_STRING} ^id=(d+)&cat=(d+)$ [NC]
RewriteRule . /index/%1/%2/? [L,R]

#rule 2
RewriteRule ^index/(d+)/(d+)/$ /index.php?id=$1&cat=$2 [L,NC]
这就是我认为正在发生的事情 从您上面引用的步骤 浏览到index.php?id = 3& cat = 5 查看位置栏阅读索引/ 3/5 / 是否从index.php?id = 3& cat = 5提供内容 在步骤1,规则1匹配并重定向到位置栏并完成步骤2。 在步骤3,规则2现在匹配并重写为index.php。 由于大卫所说的原因,规则重新运行,但由于
THE_REQUEST
一旦设置为原始请求,它就是不可变的,它仍然包含
/index/3/5
,因此规则1不匹配。 规则2也不匹配,并且提供了index.php的结果。 大多数其他变量是可变的,例如
REQUEST_URI
。它们在规则处理期间的修改以及模式匹配的不正确期望与原始请求相反是无限循环的常见原因。 它有时候感觉很深奥,但我确信其复杂性有合理的原因:-) 编辑   当然有两个不同的要求 有2个客户端请求,来自Step1的原始请求和来自步骤2中的外部重定向的请求。 我在上面说的是当规则2在第二个请求上匹配时,它被重写为/index.php并导致内部重定向。这会强制再次加载
/
目录的.htaccess文件(它可能很容易成为另一个具有不同.htaccess规则的目录)并再次重新运行所有规则。   那么......当我拿出第一条规则时,为什么这会导致请求循环? 当重新运行规则时,第一个规则现在意外地匹配,作为Rule2的重写的结果,并进行重定向,导致无限循环。 大卫的回答确实包含了大部分这些信息,这就是我所说的“出于大卫所说的原因”。 但是,这里的要点是你确实需要额外的条件,要么你的条件停止了内部重定向的进一步规则处理,要么防止规则1匹配,这是防止无限循环的必要条件。     

要回复问题请先登录注册