在 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_ROOT
和EFFECTIVE_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 Off
或 RewriteEngine On
),您的 MAJOR
变量也会消失。这是因为一旦 URL 被重写为sub/B.php
,docroot/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 中的单个规则