如何在.htaccess上下文中阻止多个mod_rewrite传递(或无限循环)

Doi*_*oin 16 .htaccess mod-rewrite redirect loops

我正在一个运行在共享Apache v2.2服务器上的网站上工作,因此所有配置都是通过.htaccess文件进行的,我想使用mod_rewrite以不太完全直接的方式将URL映射到文件系统.举个例子,让我们说我想做的是:

  • 将URL映射www.mysite.com/Alice到filesystem文件夹/public_html/Bob
  • 将URL映射www.mysite.com/Bob到filesystem文件夹/public_html/Alice

现在,经过几个小时的工作,仔细设计规则集(真正的规则集,而不是Alice/Bob的规则集!)我将所有精心设计的重写规则放在/ public_html中的.htaccess文件中,然后对其进行测试... 500服务器错误!

我被一个记录良好的"陷阱"所困扰!在Apache中:当在.htaccess文件中使用mod_rewrite规则时,重新提交重写的URL 以进行另一轮处理(就像它是外部请求一样).这样就可以应用重写请求的目标文件夹中的任何规则,但它可能会导致网络服务器出现一些非常违反直觉的行为!

在上面的示例中,这意味着请求www.mysite.com/Alice/foo.html被重写/Bob/foo.html,然后作为请求重新提交(内部)到服务器www.mysite.com/Bob/foo.html.然后重新重新写入/Alice/foo.html并重新提交,这会导致重新重写/Bob/foo.html,等等; 随之而来的是无限循环...仅由服务器超时错误破坏.


问题是,如何确保.htaccess mod_rewrite规则集仅应用于ONCE?


RewriteRule中的[L]标志在单次通过规则集期间停止所有进一步的重写,但在重新提交重新提交到服务器之后不会停止重新应用整个规则集.根据文档,Apache v2.3.9 +(目前处于Beta版)包含一个[END]标志,可以精确地提供此功能.不幸的是,网络主机仍在使用Apache 2.2,他们拒绝我的礼貌请求升级到测试版!

我们需要的是一种解决方法,它提供与[END]标志类似的功能.我的第一个想法是我可以使用一个环境变量:在第一次重写过程中设置一个标志,告诉后续的传递不再进行重写.如果我调用我的标志变量'END',代码可能如下所示:

#  Prevent further rewriting if 'END' is flagged
RewriteCond %{ENV:END} =1
RewriteRule .* - [L]

#  Map /Alice to /Bob, and /Bob to /Alice, and flag 'END' when done
RewriteRule ^Alice(/.*)?$ Bob$1 [L,E=END:1]
RewriteRule ^Bob(/.*)?$ Alice$1 [L,E=END:1]
Run Code Online (Sandbox Code Playgroud)

不幸的是,这段代码不起作用:经过一些实验,我发现环境变量在重新提交重写的URL到服务器的过程中无法生存.这个Apache文档页面的最后一行表明环境变量应该在内部重定向中存活,但我发现情况并非如此.

[ 编辑:在某些服务器上,它确实有效.如果是这样,它是比下面的更好的解决方案.您必须在自己的服务器上亲自尝试才能看到.]

尽管如此,总的想法可以得到挽救.经过几个小时的拔毛,以及同事的一些建议,我意识到HTTP请求标题在内部重定向保留的,所以如果我可以在其中一个中存储我的标志,它可能会起作用!


这是我的解决方案:


# This header flags that there's no more rewriting to be done.
# It's a kludge until use of the END flag becomes possible in Apache v2.3.9+
# ######## REMOVE this directive for Apache 2.3.9+, and change all [...,L,E=END:1]
# ######## to just [...,END] in all the rules below!

RequestHeader set SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj 1 env=END


# If our special end-of-rewriting header is set this rule blocks all further rewrites.
# ######## REMOVE this directive for Apache 2.3.9+, and change all [...,L,E=END:1]
# ######## to just [...,END] in all the rules below!

RewriteCond %{HTTP:SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj} =1 [NV]
RewriteRule .* - [L]


#  Map /Alice to /Bob, and /Bob to /Alice, and flag 'END' when done

RewriteRule ^Alice(/.*)?$ Bob$1 [L,E=END:1]
RewriteRule ^Bob(/.*)?$ Alice$1 [L,E=END:1]
Run Code Online (Sandbox Code Playgroud)

......而且,它有效!原因如下:在.htaccess文件中,与各种apache模块关联的指令以主Apache配置中定义的模块顺序执行(或者,这是我的理解,无论如何......).在这种情况下(并且关键是该解决方案的成功)mod_headers设置为在mod_rewrite之后执行,因此RequestHeader指令在重写规则之后执行.这意味着SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj如果在其标志列表中具有[E = END:1]的RewriteRule匹配,则将标头添加到HTTP请求中.在下一次传递(重新提交重新提交到服务器之后),第一个RewriteRule检测到此标头,并中止任何进一步的重写.

有关此解决方案的一些注意事项是:

  1. 如果Apache配置为 mod_rewrite 之前运行mod_headers,它将无法工作.(我不确定这是否可行,或者如果是这样,那将是多么不寻常).

  2. 如果外部用户SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj在其对服务器的HTTP请求中包含标头,则它将禁用所有 URL重写规则,并且该用户将"按原样"看到文件系统目录结构.这就是在标题名称末尾随机字符串ascii字符的原因 - 这是为了使标题难以猜测.这是一个功能还是安全漏洞取决于您的观点!

  3. 这里的想法是一种解决方法,模仿在尚未拥有它的Apache版本中使用[END]标志.如果您只想确保规则集只运行一次,无论触发哪些规则,那么您可以放弃使用'END'环境变量并执行以下操作:

    RewriteCond %{HTTP:SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj} =1 [NV]
    RewriteRule .* - [L]
    
    RequestHeader set SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj 1
    
    #  Map /Alice to /Bob, and /Bob to /Alice
    RewriteRule ^Alice(/.*)?$ Bob$1 [L]
    RewriteRule ^Bob(/.*)?$ Alice$1 [L]
    
    Run Code Online (Sandbox Code Playgroud)

    或者甚至更好,这(虽然REDIRECT_*变量在Apache v2.2文档中记录很少 - 它们似乎只在这里提到) - 所以我不能保证它适用于所有版本的Apache):

    RewriteCond %{ENV:REDIRECT_STATUS} !^$
    RewriteRule .* - [L]. 
    
    #  Map /Alice to /Bob, and /Bob to /Alice
    RewriteRule ^Alice(/.*)?$ Bob$1 [L]
    RewriteRule ^Bob(/.*)?$ Alice$1 [L]
    
    Run Code Online (Sandbox Code Playgroud)

    但是,一旦你运行Apache v2.3.9 +,我希望使用[END]标志比上面的解决方案更有效,因为(大概)它完全避免重写的URL被重新提交给服务器而不是另一个改写传球.

    请注意,您可能还希望阻止重写子请求,在这种情况下,您可以RewriteCond执行不执行任何更多重写规则,如下所示:

    RewriteCond %{ENV:REDIRECT_STATUS} !^$ [OR]
    RewriteCond %{IS_SUBREQ} =true
    RewriteRule .* - [L]
    
    Run Code Online (Sandbox Code Playgroud)
  4. 这里的想法是一个解决方法,以取代在尚未拥有它的Apache版本中使用[END]标志.但事实上,您可以使用这种通用方法来存储多个标志 - 您可以存储任意字符串或数字,这些字符串或数字将在内部服务器重定向中保留,并根据任何测试条件设计重写规则以依赖它们RuleCond提供.(我不能,在我的头脑中,想到你为什么要这样做的原因......但是,嘿,你拥有的灵活性和控制力越强,对吧?)


我想任何读过这篇文章的人都已经发现我在这里并没有真正提出问题.这更多的是我找到了我自己的问题的解决方案,并希望在此处发布以供参考,以防其他人遇到同样的问题.这是这个网站的重要组成部分,对吧?

...

但由于这应该是一个提问和回答的论坛,我会问:

  • 任何人都可以看到这个解决方案的任何潜在问题(除了我已经提到的那些)?
  • 或者有没有人有更好的方法来实现同样的事情?

Laz*_*One 7

根据您的Apache构建,这种情况可能有效(将其添加到"停止重写"规则:ie RewriteRule .* - [L]..或仅针对特定的有问题的规则):

RewriteCond %{ENV:REDIRECT_STATUS} ^$
Run Code Online (Sandbox Code Playgroud)

REDIRECT_STATUS将没有第一次/初始重写,并且200在任何后续周期中将具有值(或者可能还有其他值 - 没有检查到那么深).

不幸的是,它适用于某些系统,而不适用于其他系统,我个人不知道是什么负责使其工作.

除此之外,最常见的是添加重写条件来检查原始URL,例如通过解析%{THE_REQUEST}变量例如RewriteCond %{THE_REQUEST} ^[A-Z]+\s.+\.php\sHTTP/.+- 但这仅对个别有问题的规则有意义.

一般来说 - 你应该避免这样的"重写A - > B然后B - > A"情况(我很确定你知道这一点).

至于你自己的解决方案 - "如果不破坏就不要修复" - 如果它有效,那么它很棒,因为我没有看到这种方法有任何重大问题.