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

时间:2011-04-07 00:14:15

标签: .htaccess http-redirect

今天早些时候,我正在帮助有.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格式,并让用户总是看到美化后的格式。

经过多次转发,我们提出了以下.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]),而且一切都会变成肉汁。

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

2 个答案:

答案 0 :(得分:4)

无法修改您的设置,我不能肯定地说,但我相信这个问题是由于mod_rewrite的以下相对神秘的功能:

  

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

(来源:mod_rewrite technical documentation,我高度建议阅读此内容)

换句话说,当您在RewriteRule文件中使用.htaccess时,新的重写URL可能会映射到文件系统上完全不同的目录,在这种情况下{{1原始目录中的文件将不再适用。因此,只要.htaccess文件中的RewriteRule与请求匹配,Apache就必须使用修改后的URL重新开始处理从头开始。这意味着,除了其他事项外,每次.htaccess都会被再次检查。

在您的情况下,您会从浏览器访问RewriteRule/index/X/Y/文件中的最后一条规则会触发,将其重写为.htaccess,因此Apache必须使用网址/index.php?id=X&cat=Y创建新的内部子请求。这符合您之前的外部重定向规则,因此Apache将302响应发送回浏览器以将其重定向到/index.php?id=X&cat=Y。但请记住,浏览器从未见过内部子请求;据他所知,它已经在/index/X/Y/。所以它看起来好像你被从/index/X/Y/重定向到同一个URL,触发了一个无限循环。

除了性能损失之外,这可能是您应该尽可能避免在/index/X/Y/文件中添加重写规则的更好理由之一。如果将这些规则移动到主服务器配置,则不会出现此问题,因为规则上的匹配不会触发内部子请求。如果您无法访问主服务器配置文件,可以通过一种方式解决它( EDIT :或者我认为,虽然它似乎不起作用 - 请参阅注释)是通过将.htaccess(无子请求)标记添加到外部重定向规则

[NS]

一旦这样做,您就不再需要检查RewriteRule ^index\.php$ http://example.com/index/%1/%2/? [L,R,NS] 的第一条规则。

答案 1 :(得分:0)

以下解决方案对我有用。

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& cat = 5
  2. 请参阅位置栏阅读索引/ 3/5 /
  3. 从index.php?id = 3& cat = 5
  4. 获取内容

    在步骤1,规则1匹配并重定向到位置栏并完成步骤2.

    在第3步,规则2现在匹配并重写为index.php。

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

    规则2也不匹配,并且提供了index.php的结果。

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

    有时它感觉非常深奥,但我确信其复杂性有合理的原因: - )

    修改

      

    当然有两个不同的请求

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

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

      

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

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

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

    然而,这里的要点是你确实需要额外的条件,要么你的条件停止了内部重定向的进一步规则处理,要么防止规则1匹配,这是防止无限循环的必要条件。 / p>