在Apache RewriteRule指令中设置环境变量时,是什么导致变量名称以“ REDIRECT_”为前缀?


74

我正在尝试使用[E=VAR:VAL].htaccess文件中RewriteRule规则上的标志设置Apache环境变量(供PHP使用)。

我已经发现在PHP中将变量作为服务器变量$_SERVER而不是$_ENV(在一定意义上)进行访问。但是,我的问题是对于某些规则,该[E=VAR:VAL]标记按预期工作,并且我以变量结尾,$_SERVER['VAR']但是对于其他规则,我以变量$_SERVER['REDIRECT_VAR']$_SERVER['REDIRECT_REDIRECT_VAR']等等结尾。

A.是什么导致在Apache中使用该[E=VAR:VAL]标志设置的环境变量通过在变量名前加上“ REDIRECT_”来重命名的?

B.我该怎么做才能确保我得到的环境变量的名称没有更改,因此我可以在PHP中访问它,$_SERVER['VAR']而无需诉诸检查变量名的变体,该变量名带有多个“ REDIRECT_”实例要吗?

找到部分解决方案。如果需要,将以下内容添加到重写规则的开头会在每个重定向上重新创建原始的ENV:VAR(以及在其中保留REDIRECT_VAR版本):

RewriteCond %{ENV:REDIRECT_VAR} !^$
RewriteRule .* - [E=VAR:%{ENV:REDIRECT_VAR}]

我一直使用getenv()- php.net/manual/en/function.getenv.php,并且还没有遇到过任何奇怪的问题。
查利,2016年

Answers:


75

这种行为是不幸的,甚至没有被记录在案。

.htaccess每个目录上下文

这是.htaccess每个目录(每个目录)上下文中似乎发生的情况:

假设Apache处理.htaccess包含重写指令的文件。

  1. Apache使用所有标准CGI / Apache变量填充其环境变量映射

  2. 重写开始

  3. 环境变量在RewriteRule指令 中设置

  4. 当Apache停止处理RewriteRule指令(由于L标志或规则集的结尾)并且URL已被更改时RewriteRule,Apache将重新启动请求处理。

    如果您不熟悉此部分,请参阅L标志文档

    因此,规则集可以从头开始再次运行。如果其中一个规则导致重定向(内部或外部)导致请求过程重新开始,则通常会发生这种情况。
  5. 从我的观察中,我相信当发生#4时,重复#1,然后在RewriteRule指令中设置的环境变量将被REDIRECT_添加到环境var映射之前并添加到环境var映射中(不一定按此顺序,但是最终结果包括的组合)。

    这是清除所选变量名的步骤,稍后我将解释为什么它如此重要和不便。

恢复变量名

当我最初遇到此问题时,我在.htaccess(简体)中做了如下操作:

RewriteCond %{HTTP_HOST} (.+)\.projects\.

RewriteRule (.*) subdomains/%1/docroot/$1

RewriteRule (.+/docroot)/ - [L,E=EFFECTIVE_DOCUMENT_ROOT:$1]

如果要在第一个环境变量中设置环境变量RewriteRule,Apache将重新启动重写过程,并在变量前面加上REDIRECT_(上面的步骤4和5),因此我将无法通过分配的名称访问它。

在这种情况下,第一个RewriteRule更改URL,因此在RewriteRule处理完两个后,Apache重新启动该过程并.htaccess再次处理。第二次,RewriteRule由于该RewriteCond指令而跳过了第一次,但是第二次RewriteRule匹配,再次设置了环境变量,并且重要的是,它没有更改URL。因此,请求/重写过程不会重新开始,因此我选择的变量名将保持不变。在这种情况下,我实际上同时拥有REDIRECT_EFFECTIVE_DOCUMENT_ROOTEFFECTIVE_DOCUMENT_ROOT。如果我首先使用L标志RewriteRule,我只会有EFFECTIVE_DOCUMENT_ROOT

@trowel的部分解决方案的工作原理类似:再次处理重写指令,将重命名的变量再次分配给原始名称,并且如果URL不变,则过程结束并且分配的变量名称保持不变。

为什么这些技术不足

这两种技术都有一个主要缺陷:当.htaccess您在设置环境变量的文件中的重写规则将URL重写到一个嵌套更深的目录中,该目录具有.htaccess可以进行任何重写的文件时,您分配的变量名将再次被擦除。

假设您具有这样的目录布局:

docroot/
        .htaccess
        A.php
        B.php
        sub/
                .htaccess
                A.php
                B.php

docroot/.htaccess这样的:

RewriteRule ^A\.php sub/B.php [L]

RewriteRule .* - [E=MAJOR:flaw]

因此,您可以请求/A.php,并将其重写为sub/B.php。您仍然有MAJOR变量。

但是,如果您在中有任何重写指令docroot/sub/.htaccess(甚至justRewriteEngine OffRewriteEngine On),MAJOR变量都会消失。这是因为一旦将URL重写为sub/B.phpdocroot/sub/.htaccess便会对其进行处理,并且如果其中包含任何重写指令,docroot/.htaccess则不会再次处理其中的重写指令。如果您有一个REDIRECT_MAJORafter后docroot/.htaccess处理(例如,如果省略了Lfirst中的标志RewriteRule),您仍然会拥有它,但是这些指令不会再次运行以设置您选择的变量名。

遗产

因此,说您想:

  1. RewriteRule在目录树的特定级别的指令中 设置环境变量(例如docroot/.htaccess

  2. 在更深层次的脚本中提供它们

  3. 使它们具有分配的名称

  4. 能够在更深层嵌套的.htaccess文件中 具有重写指令

一种可能的解决方案是RewriteOptions inherit在嵌套更深的.htaccess文件中使用指令。这样,您就可以在嵌套程度较低的文件中重新运行rewrite指令,并使用上面概述的技术来设置具有所选名称的变量。但是,请注意,这会增加复杂性,因为您必须更加谨慎地在嵌套程度较低的文件中编写重写指令,以使它们在从嵌套程度更高的目录中再次运行时不会引起问题。我相信Apache会为嵌套深度更深的目录除去per-dir前缀,并在嵌套深度较小的文件中对该值运行rewrite指令。

@trowel的技术

据我所知,似乎并没有证明支持%{ENV:REDIRECT_VAR}RewriteRule E标志的值组件(例如[E=VAR:%{ENV:REDIRECT_VAR}])中使用类似的构造:

VAL可能包含将被扩展的反向引用($ N或%N)。

它确实可以工作,但是如果您要避免依赖未记录的内容(如果我对此有误,请纠正我),可以这样轻松地完成它:

RewriteCond %{ENV:REDIRECT_VAR} (.+)
RewriteRule .* - [E=VAR:%1]

设置环境

我不建议依赖此方法,因为它似乎与所记录的行为不一致(请参见下文),但是(在docroot/.htaccessApache 2.2.20中,此方法)对我有用:

SetEnvIf REDIRECT_VAR (.+) VAR=$1

只有早期的SetEnvIf [NoCase]指令定义的那些环境变量才能以这种方式进行测试。

为什么?

我不知道为这些名称加上前缀的理由REDIRECT_是什么-不足为奇,因为在Apache文档部分中似乎没有提及mod_rewrite指令RewriteRule标志环境变量

目前,对于我来说,这似乎是一个很大的麻烦,因为没有解释为什么它比单独分配名称更好的解释。缺乏文档只会加剧我对此的怀疑。

能够在重写规则中分配环境变量很有用,或者至少是有用的。但是,这种改名行为大大降低了其实用性。这篇文章的复杂性说明了这种行为以及为克服它而必须克服的困难。


遇到了另一个规则,该规则属于令人讨厌的无证怪异类别。在某些服务器上,环境变量名称必须以HTTP_为前缀。(stackoverflow.com/questions/17073144/…)–
克里斯

抱歉,我听不懂一件事。在您的示例中,如果规则是: RewriteRule ^A\.php sub/B.php [L]然后RewriteRule .* - [E=MAJOR:flaw],如果第一个规则匹配,MAJOR则不会设置,因为第一个规则匹配并且是最后一个(L标志)。重写URL,并在mod_rewrite的内部循环上再次重申apache ,这次匹配RewriteRule .* - [E=MAJOR:flaw],设置了环境变量,然后才进入下一个.htaccess内部/sub。这是对的吗?
tonix '16

@tonix从我在这里写的内容来看,不,听起来不正确。按照我的描述方式,首先RewriteRule将URL重写为sub/B.php,然后结束它的争夺docroot/.htaccess:env变量从未真正被设置。(将env变量描述为“消失”可能不是最好的放置方法。)同样,这就是是否存在一个sub/.htaccess包含任何与重写有关的指令的变量。
JMM

从此处获取:重写路径(在本例中为)内的stackoverflow.com/: **If the URI changed L will re-inject into the the next round (the outer loop)** So I guess that if a URL is rewritten with [L] flag set, mod_rewrite will pass to the next round, but this time taking into consideration the most inner questions / 11484739/… .htaccess`文件sub/.htaccess。至少,这是我从您的答案以及我链接的答案中所了解的。
tonix

3
FWIW,以下是一些Apache文档,其中明确讨论了“ REDIRECT_”前缀:httpd.apache.org/docs/2.0/custom-error.html。但是,我很同情它—我自己被它咬了。
jwd

8

我根本没有测试过,我知道它不能解决点A或B的问题,但是PHP文档中的注释中对此问题进行了一些说明,并提供了一些使用以下方式访问这些变量的解决方案$_SERVER['VAR']

http://www.php.net/manual/zh/reserved.variables.php#79811

编辑-对提供的问题的更多答复:

答:如果环境变量涉及重定向,则Apache将它们重命名。例如,如果您具有以下规则:

RewriteRule ^index.php - [E=VAR1:'hello',E=VAR2:'world']

然后,您可以使用$_SERVER['VAR1']和访问VAR1和VAR2 $_SERVER['VAR2']。但是,如果您像这样重定向页面:

RewriteRule ^index.php index2.php [E=VAR1:'hello',E=VAR2:'world']

然后,您必须使用$_SERVER['REDIRECT_VAR1'],等等。

B:解决此问题的最佳方法是处理您对使用PHP感兴趣的变量。创建一个贯穿该$_SERVER数组并查找所需项目的函数。您甚至可以使用如下功能:

function myGetEnv($key) {
    $prefix = "REDIRECT_";
    if(array_key_exists($key, $_SERVER))
        return $_SERVER[$key];
    foreach($_SERVER as $k=>$v) {
        if(substr($k, 0, strlen($prefix)) == $prefix) {
            if(substr($k, -(strlen($key))) == $key)
                return $v;
        }
    }
    return null;
}

感谢您的评论链接。他们确认了问题并提供了解决方法,但是它只是将问题从PHP转换为.htaccess中的其他代码,我希望Apache专家可能知道如何使该名称持久化,因此不需要解决方法代码!(我会给您+1,但没有足够的代表来做)
抹子2010年

@trowel-查看我的答案的变化
thetaiko 2010年

感谢thetaiko,它重现了Apache 2.0中引入的一项功能,该功能旨在帮助通过重定向跟踪变量。不错的PHP函数,但我可能会使用以/ ^($ REDIRECT _)*开头的preg_match,以在一击中捕获任意数量的重定向。在apache端,我找到了解决方案(增加了问题)
抹子2010年

1

因为我不想改变我的任何代码(我也不能更改使用的库的代码),我就用下面的办法:自举,而我的应用程序-在我如index.php-我返工的$_ENV超全局,这样的变量前缀REDIRECT_是改写成它们的正常预期名称:

// Fix ENV vars getting prepended with `REDIRECT_` by Apache
foreach ($_ENV as $key => $value) {
    if (substr($key, 0, 9) === 'REDIRECT_') {
        $_ENV[str_replace('REDIRECT_', '', $key)] = $value;
        putenv(str_replace('REDIRECT_', '', $key) . '=' . $value);
    }
}

我们不仅可以直接设置它$_ENV,还可以使用来存储它putenv()。这样,可能使用的现有代码和库getenv()可以正常工作。


附带说明:如果要HTTP_AUTHORIZATION在代码中提取标头(例如),则需要对进行相同的处理$_SERVER

foreach ($_SERVER as $key => $value) {
    if (substr($key, 0, 9) === 'REDIRECT_') {
        $_SERVER[str_replace('REDIRECT_', '', $key)] = $value;
    }
}

str_replace('REDIRECT_', '', $key)必须preg_replace('/^(REDIRECT\_)*/', '', $key)仅替换索引的开头
d9k
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.