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

问题描述:

今天早些时候,我正在帮助有人使用.htaccess用例,并came up with a solution有效,但不能自己弄明白!这为什么会导致无限的请求循环?

他希望能够:

  • 浏览index.php?id=3&cat=5
  • 看到地址栏阅读index/3/5/
  • 让内容从index.php?id=3&cat=5

担任最后两个步骤都相当典型的(通常来自用户首先输入index/3/5),但第一步是必需的,因为他仍然有他网站中的一些旧格式链接,无论出于何种原因,都无法改变它们。所以他需要支持这两个的URL格式,并让用户总是最终看到美化的。

后多少-ING回回-ING,我们提出了以下.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]),并且所有的事情都会变成肉汁。

所以...为什么这会导致请求循环时,我拿出第一条规则?

+1

在我看来,并不奇怪的用法 – Cyclone 2011-04-07 00:16:10

没有能够与您的设置鼓捣,我不能肯定地说,但我相信这个问题是由于mod_rewrite的以下比较神秘的功能:

当你操纵URL /文件名在每个目录的上下文中,mod_rewrite会首先将文件名重新写回到其相应的URL(通常不可能,但请参阅下面的RewriteBase指令以实现此目的),然后用新URL启动一个新的内部子请求。这将重新开始处理API阶段。

(来源:mod_rewrite technical documentation,我高度推荐阅读本)

换句话说,当你在一个.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](no子请求)标志,以你的外部重定向规则,

RewriteRule ^index\.php$ http://example.com/index/%1/%2/? [L,R,NS] 

一旦你这样做,你应该不再需要该检查REDIRECT_STATUS的第一条规则。

+0

你是,只有一天,我的英雄和救世主。 – 2011-04-07 08:42:19

+0

(虽然在第一次检查时,任何一条规则上的“NS”似乎都没有什么区别) – 2011-04-07 08:44:52

+0

不管怎么样,我会接受它......如果没有别的,你提供了关于最可能的原因的一个很好的解释,这是我以后的事。谢谢 – 2011-04-07 22:30:30

下面的解决方案为我工作。

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] 

以下是我认为正在发生的

从你上面

  1. 浏览引用到index.php步骤?ID = 3 &猫= 5
  2. 见位置栏阅读指数/ 3/5/
  3. 是否有从index.php提供的内容?id = 3 & cat = 5

在步骤1中,规则1场比赛和重定向到位置栏,并满足步骤2.

在步骤3中,规则2现在匹配并且重写到index.php。

由于David陈述的原因,规则重新运行,但由于THE_REQUEST一旦设置为原始请求就不可变,它仍然包含/index/3/5因此规则1不匹配。

规则2不匹配,index.php的结果被提供。

大多数其他变量是可变的,例如, REQUEST_URI。它们在规则处理期间的修改以及模式匹配的错误期望与原始请求相反是无限循环的常见原因。

它的感觉相当深奥的时候,但是我肯定有其复杂的逻辑原因:-)

编辑

当然有两种截然不同的请求

有2个客户端请求,来自Step1的原始请求和来自步骤2的外部重定向的请求。

我上面提到的是,当第二个请求中的规则2匹配时,它被重写为/index.php并导致内部重定向。这会强制/目录的.htaccess文件再次加载(它可能很容易成为具有不同.htaccess规则的另一个目录),并再次运行所有规则。

所以......当我拿出第一条规则时,为什么会导致请求循环?

当规则重新运行时,由于Rule2的重写,第一条规则现在意外地匹配,并进行重定向,导致无限循环。

大卫的回答确实包含了大部分这些信息,我的意思是“出于大卫说的原因”。

但是,这里的要点是,您确实需要额外的条件,无论是您的条件,停止进一步的规则处理内部重定向,或我的,防止规则1匹配,是必要的,以防止无限循环。

+0

当然有两个不同的请求 – 2012-01-08 17:48:04

+0

@LightnessRacesinOrbit see edit – 2012-01-08 21:13:14