今天早些时候,我正在帮助有.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]
),而且一切都会变成肉汁。
那么......当我拿出第一条规则时,为什么会导致请求循环呢?
答案 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,规则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>