为啥这会导致无限请求循环?

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 不会导致无限递归?

为啥这个 Scanf 会导致无限循环?

ESLint 希望 setSate 作为 useEffect 的依赖项,但这会导致无限循环(react-hooks/exhaustive-deps)