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

Posted

技术标签:

【中文标题】在 Apache RewriteRule 指令中设置环境变量时,是啥导致变量名称以“REDIRECT_”为前缀?【英文标题】:When setting environment variables in Apache RewriteRule directives, what causes the variable name to be prefixed with "REDIRECT_"?在 Apache RewriteRule 指令中设置环境变量时,是什么导致变量名称以“REDIRECT_”为前缀? 【发布时间】:2011-03-04 06:54:32 【问题描述】:

我正在尝试使用 .htaccess 文件中 RewriteRule 规则上的 [E=VAR:VAL] 标志设置 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 并且还没有遇到任何奇怪的问题。 【参考方案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) 才能仅替换索引的开头【参考方案2】:

这种行为很不幸,甚至似乎没有记录在案。

.htaccess 每个目录上下文

这是.htaccess per-directory (per-dir) 上下文中似乎发生的情况:

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

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

    重写开始

    环境变量在RewriteRule 指令中设置

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

    如果您不熟悉这部分,请参阅L flag documentation:

    因此规则集可以从头开始再次运行。最常见的情况是,如果其中一个规则导致重定向(内部或外部)导致请求过程重新开始。

    据我观察,我相信当 #4 发生时,#1 会重复,然后在 RewriteRule 指令中设置的环境变量前面加上 REDIRECT_ 并添加到环境变量映射中(不一定按此顺序,但最终结果由该组合组成)。

    这一步是删除所选变量名称的地方,稍后我将解释为什么这如此重要和不方便。

恢复变量名

当我最初遇到这个问题时,我在.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。第二次,由于 RewriteCond 指令,第一个 RewriteRule 被跳过,但第二个 RewriteRule 匹配,设置环境变量(再次),重要的是,不会更改 URL时间>。所以请求/重写过程不会重新开始,我选择的变量名会粘住。在这种情况下,我实际上同时拥有REDIRECT_EFFECTIVE_DOCUMENT_ROOTEFFECTIVE_DOCUMENT_ROOT。如果我在第一个RewriteRule 上使用L 标志,我将只有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 中有任何重写指令(即使只是 RewriteEngine OffRewriteEngine On),您的 MAJOR 变量也会消失。这是因为一旦 URL 被重写为sub/B.phpdocroot/sub/.htaccess 就会被处理,如果它包含任何重写指令,docroot/.htaccess 中的重写指令不会再次被处理。如果在处理了docroot/.htaccess 之后您有一个REDIRECT_MAJOR(例如,如果您从第一个RewriteRule 中省略了L 标志),您仍然会拥有它,但这些指令不会再次运行来设置您选择的变量名。

继承

所以,假设你想要:

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

    让它们在更深层次的脚本中可用

    让它们以指定的名称可用

    能够在嵌套更深的.htaccess 文件中使用重写指令

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

@trowel 的技巧

据我所见,在RewriteRule E 标志(例如[E=VAR:%ENV:REDIRECT_VAR])的值组件中使用类似%ENV:REDIRECT_VAR 的构造似乎不支持documented:

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

它似乎确实有效,但如果您想避免依赖未记录的内容(如果我错了,请纠正我),它可以很容易地通过这种方式完成:

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

SetEnvIf

我不建议依赖这个,因为它似乎与 documented behavior 不一致(见下文),但这个(在 docroot/.htaccess 中,使用 Apache 2.2.20)对我有用:

SetEnvIf REDIRECT_VAR (.+) VAR=$1

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

为什么?

我不知道在这些名称前加上 REDIRECT_ 的理由是什么——这并不奇怪,因为在 mod_rewrite directives、RewriteRule flags 或 @ 的 Apache 文档部分中似乎没有提到它987654326@.

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

能够在重写规则中分配环境变量是有用的,或者至少是这样。但是这种更改名称的行为大大降低了实用性。这篇文章的复杂性说明了这种行为是多么的疯狂,以及为了克服它而必须跳过的障碍。

【讨论】:

遇到了另一条属于令人讨厌的无证怪异类别的规则。在某些服务器上,环境变量名称必须以 HTTP_ 为前缀。 (***.com/questions/17073144/…) 对不起,我没听懂一件事。在您的示例中,如果规则是:RewriteRule ^A\.php sub/B.php [L],然后是 RewriteRule .* - [E=MAJOR:flaw],如果第一个规则匹配,则不会设置 MAJOR,因为第一个规则匹配并且是最后一个(L 标志)。 URL 被重写,apache 重新遍历mod_rewrite 的内部循环,这一次匹配RewriteRule .* - [E=MAJOR:flaw],设置环境变量,然后才在/sub 内的下一个.htaccess 之后进行。对吗? @tonix 只是从我在这里写的内容来看,不,这听起来不正确。按照我的描述,第一个RewriteRule 将URL 重写为sub/B.php,然后docroot/.htaccess 的游戏结束了:env 变量实际上从未被设置。 (描述为 env 变量“消失”可能不是最好的表达方式。)同样,如果有一个 sub/.htaccess 包含任何与重写相关的指令。 取自此处:***.com/questions/11484739/…: **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 .htaccess` 文件位于重写路径内(在本例中为 sub/.htaccess)。至少,这是我从你的回答和我链接的答案中理解的。 FWIW,这里有一些明确谈论“REDIRECT_”前缀的 Apache 文档:httpd.apache.org/docs/2.0/custom-error.html。不过,我很同情它的烦人——我自己只是被它咬了。【参考方案3】:

我根本没有对此进行测试,我知道它没有解决点 A 或 B,但是 PHP 文档中的 cmets 中有一些关于这个问题的描述,以及使用 $_SERVER['VAR'] 访问这些变量的一些可能的解决方案:

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

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

答:如果环境变量参与重定向,Apache 会对其进行重命名。例如,如果您有以下规则:

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

然后您可以使用$_SERVER['VAR1']$_SERVER['VAR2'] 访问VAR1 和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;

【讨论】:

感谢 cmets 的链接。他们确认了问题并确实提供了解决方法,但它只是将问题从 PHP 转移到 .htaccess 中的额外代码我希望 Apache 大师可能知道如何使名称持久,因此不需要解决方法代码! (我会给你 +1,但没有足够的代表来做) @trowel - 查看我的答案的变化 感谢 thetaiko,它表明重命名是 Apache 2.0 中引入的一项功能,旨在帮助通过重定向跟踪变量。不错的 PHP 函数,但我可能会使用以 /^($REDIRECT_)* 开头的 preg_match 来一次捕获任意数量的重定向。在 apache 结束时,我找到了一个解决方案(添加到问题中)

以上是关于在 Apache RewriteRule 指令中设置环境变量时,是啥导致变量名称以“REDIRECT_”为前缀?的主要内容,如果未能解决你的问题,请参考以下文章

RewriteCond 和RewriteRule规则说明 (转)

Apache的Mod_rewrite学习(RewriteRule重写规则的语法) 转

如何在 Apache 2.4 cookie 中设置 unix 时间戳?

将多个 RewriteRule 指令组合成 .htaccess 中的单个规则

URL重写:RewriteCond指令与RewriteRule 指令格式

如何在 apache 2.4 RewriteRule 中使用发布数据规则编写替换和重定向 url(两者同时)?