这种行为很不幸,甚至似乎没有记录在案。
.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@.
目前这对我来说似乎是一个很大的麻烦,因为没有解释为什么它比不理会分配的名称更好。缺乏文档只会加剧我对此的怀疑。
能够在重写规则中分配环境变量是有用的,或者至少是这样。但是这种更改名称的行为大大降低了实用性。这篇文章的复杂性说明了这种行为是多么的疯狂,以及为了克服它而必须跳过的障碍。