为啥这会导致无限请求循环?
Posted
技术标签:
【中文标题】为啥这会导致无限请求循环?【英文标题】:Why does this cause an infinite request loop?为什么这会导致无限请求循环? 【发布时间】:2011-07-31 06:34:22 【问题描述】:今天早些时候,我正在帮助某人处理 .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]
),一切都将变成肉汁。
那么...为什么当我取出第一条规则时会导致请求循环?
【问题讨论】:
在我看来一点也不奇怪 【参考方案1】:以下解决方案对我有用。
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。
出于 David 所述的原因,规则会重新运行,但由于 THE_REQUEST
在设置为原始请求后是不可变的,因此它仍然包含 /index/3/5
,因此规则 1 不匹配。
规则 2 也不匹配,并且提供 index.php 的结果。
大多数其他变量是可变的,例如REQUEST_URI
。它们在规则处理期间的修改,以及模式匹配与原始请求相悖的错误预期是无限循环的常见原因。
它有时感觉很深奥,但我相信它的复杂性是有合理的原因的 :-)
编辑
肯定有两个不同的请求
有 2 个客户端请求,一个来自步骤 1 的原始请求,一个来自步骤 2 中的外部重定向。
我在上面提到的是,当规则 2 在第二个请求上匹配时,它会被重写为 /index.php 并导致内部重定向。这会强制重新加载 /
目录的 .htaccess 文件(它很可能是具有不同 .htaccess 规则的另一个目录)并再次重新运行所有规则。
那么...为什么当我取出第一条规则时会导致请求循环?
重新运行规则时,由于 Rule2 的重写,第一条规则现在意外匹配,并执行重定向,从而导致无限循环。
大卫的回答确实包含大部分信息,这就是我的意思“出于大卫所说的原因”。
但是,这里的重点是您确实需要额外的条件,要么是您的条件,它会停止对内部重定向的进一步规则处理,要么是我的条件,它阻止规则 1 匹配,以防止无限循环。
【讨论】:
肯定有两个不同的请求【参考方案2】:由于无法修改您的设置,我不能肯定地说,但我相信这个问题是由于 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
文件中的更好理由之一。如果将这些规则移至主服务器配置,则不会出现此问题,因为规则上的匹配不会触发内部子请求。如果您无权访问主服务器配置文件,则可以绕过它的一种方法(EDIT:或者我认为,尽管它似乎不起作用 - 请参阅 cmets)是将[NS]
(无子请求)标志添加到您的外部重定向规则中,
RewriteRule ^index\.php$ http://example.com/index/%1/%2/? [L,R,NS]
一旦你这样做了,你应该不再需要检查REDIRECT_STATUS
的第一条规则。
【讨论】:
你是我的英雄和救世主。 (虽然,乍一看,NS
对任一规则似乎都没有影响。)
嗯,无论如何我都会接受它......如果没有别的,你对最可能的原因提供了很好的解释,这就是我所追求的。谢谢
@Tomalak:我以为我昨天在这里发表了评论,但我想它没有通过......无论如何,Apache 是一个复杂的系统,很难理解这一切;-)有时我会求助于RewriteLog
;如果启用该指令,则 mod_rewrite 日志文件中的输出会准确显示每个重写规则的作用,并且从中基本上总是可以找出发生了什么(例如,为什么 [NS]
不起作用)。但是您已经有了一个可行的解决方案,所以我想调查[NS]
问题并不那么重要。
@David:我真的很想弄清楚这一点;唉,我正在努力获得任何日志输出。以上是关于为啥这会导致无限请求循环?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在循环开始时调用 requestAnimationFrame 不会导致无限递归?
ESLint 希望 setSate 作为 useEffect 的依赖项,但这会导致无限循环(react-hooks/exhaustive-deps)