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

发布于 2024-09-05 23:55:47 字数 797 浏览 3 评论 0 原文

我正在尝试使用 .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}]

I am trying to set Apache environment variables (for use in PHP) with the [E=VAR:VAL] flag on RewriteRule rules in an .htaccess file.

I have already discovered the variables are accessed in PHP as server variables $_SERVER rather than $_ENV (which makes a certain amount of sense). However, my problem is for some rules the [E=VAR:VAL] flag works as expected and I end up with a variable $_SERVER['VAR'] but for other rules I end with a variable $_SERVER['REDIRECT_VAR'] or $_SERVER['REDIRECT_REDIRECT_VAR'], etc

A. What causes an environment variable set in Apache using the [E=VAR:VAL] flag to get renamed by having "REDIRECT_" prepended to the variable name?

B. What can I do to make sure I end up with an Environment Variable with an unchanged name so I can access it in PHP as $_SERVER['VAR'] without having to resort to checking for variations of the variable name having one of more instances of "REDIRECT_" prepended to it?

Partial solution found. Adding the following to the start of the rewrite rules recreates the original ENV:VAR on each redirect (as well as leaving the REDIRECT_VAR versions there) if they're needed:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

久伴你 2024-09-12 23:55:47

这种行为是不幸的,甚至似乎没有被记录下来。

.htaccess 每个目录上下文

以下是 .htaccess 每个目录 (per-dir) 上下文中发生的情况:

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

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

  2. 重写开始

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

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

    如果您不熟悉这部分,请参阅 L< /code> 标记文档

    <块引用>
    因此规则集可以从头开始再次运行。最常见的是,如果规则之一导致重定向(无论是内部还是外部),从而导致请求过程重新开始,就会发生这种情况。

  5. 据我观察,我相信当 #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 标志) code>),您仍然会拥有它,但这些指令不会再次运行来设置您选择的变量名称。

继承

那么,假设您想要:

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

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

  3. 让它们具有指定的名称

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

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

@trowel 的技术

据我所知,支持在 RewriteRule E 的值组件中使用像 %{ENV:REDIRECT_VAR} 这样的构造> 标志(例如 [E=VAR:%{ENV:REDIRECT_VAR}])似乎不是 记录

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

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

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

SetEnvIf

我不建议依赖于此,因为它似乎与记录的行为不一致(请参阅下面),但这(在 docroot/.htaccess 中,使用 Apache 2.2.20)对我有用:

SetEnvIf REDIRECT_VAR (.+) VAR=$1

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

为什么?

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

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

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

This behavior is unfortunate and doesn't even appear to be documented.

.htaccess per-dir context

Here's what appears to happen in .htaccess per-directory (per-dir) context:

Assume that Apache processes an .htaccess file that includes rewrite directives.

  1. Apache populates its environment variable map with all of the standard CGI / Apache variables

  2. Rewriting begins

  3. Environment variables are set in RewriteRule directives

  4. When Apache stops processing the RewriteRule directives (because of an L flag or the end of the ruleset) and the URL has been changed by a RewriteRule, Apache restarts request processing.

    If you're not familiar with this part, see the L flag documentation:

    thus the ruleset may be run again from the start. Most commonly this will happen if one of the rules causes a redirect - either internal or external - causing the request process to start over.

  5. From what I can observe, I believe that when #4 happens, #1 is repeated, then the environment variables that were set in RewriteRule directives are prepended with REDIRECT_ and added to the environment vars map (not necessarily in that order, but the end result consisting of that combination).

    This step is where the chosen variable names are wiped out, and in a moment I will explain why that is so important and inconvenient.

Restoring variable names

When I originally ran into this issue, I was doing something like the following in .htaccess (simplified):

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

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

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

If I were to set the environment variable in the first RewriteRule, Apache would restart the rewriting process and prepend the variable with REDIRECT_ (steps #4 & 5 above), thus I'd lose access to it via the name I assigned.

In this case, the first RewriteRule changes the URL, so after both RewriteRules are processed, Apache restarts the procedure and processes the .htaccess again. The second time, the first RewriteRule is skipped because of the RewriteCond directive, but the second RewriteRule matches, sets the environment variable (again), and, importantly, doesn't change the URL. So the request / rewriting process does not start over, and the variable name I chose sticks. In this case I actually have both REDIRECT_EFFECTIVE_DOCUMENT_ROOT and EFFECTIVE_DOCUMENT_ROOT. If I were to use an L flag on the first RewriteRule, I'd only have EFFECTIVE_DOCUMENT_ROOT.

@trowel's partial solution works similarly: the rewrite directives are processed again, the renamed variable is assigned to the original name again, and if the URL does not change, the process is over and the assigned variable name sticks.

Why those techniques are inadequate

Both of those techniques suffer from a major flaw: when the rewrite rules in the .htaccess file where you set environment variables rewrite the URL to a more deeply nested directory that has an .htaccess file that does any rewriting, your assigned variable name is wiped out again.

Say you have a directory layout like this:

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

And a docroot/.htaccess like this:

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

RewriteRule .* - [E=MAJOR:flaw]

So you request /A.php, and it's rewritten to sub/B.php. You still have your MAJOR variable.

However, if you have any rewrite directives in docroot/sub/.htaccess (even just RewriteEngine Off or RewriteEngine On), your MAJOR variable disappears. That's because once the URL is rewritten to sub/B.php, docroot/sub/.htaccess is processed, and if it contains any rewrite directives, rewrite directives in docroot/.htaccess are not processed again. If you had a REDIRECT_MAJOR after docroot/.htaccess was processed (e.g. if you omit the L flag from the first RewriteRule), you'll still have it, but those directives won't run again to set your chosen variable name.

Inheritance

So, say you want to:

  1. set environment variables in RewriteRule directives at a particular level of the directory tree (like docroot/.htaccess)

  2. have them available in scripts at deeper levels

  3. have them available with the assigned names

  4. be able to have rewrite directives in more deeply nested .htaccess files

A possible solution is to use RewriteOptions inherit directives in the more deeply nested .htaccess files. That allows you to re-run the rewrite directives in less deeply nested files and use the techniques outlined above to set the variables with the chosen names. However, note that this increases complexity because you have to be more careful crafting the rewrite directives in the less deeply nested files so that they don't cause problems when run again from the more deeply nested directories. I believe Apache strips the per-dir prefix for the more deeply nested directory and runs the rewrite directives in the less deeply nested files on that value.

@trowel's technique

As far as I can see, support for using a construct like %{ENV:REDIRECT_VAR} in the value component of a RewriteRule E flag (e.g. [E=VAR:%{ENV:REDIRECT_VAR}]) does not appear to be documented:

VAL may contain backreferences ($N or %N) which will be expanded.

It does appear to work, but if you want to avoid relying on something undocumented (please correct me if I'm wrong about that), it can easily be done this way instead:

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

SetEnvIf

I don't recommend relying on this, because it doesn't seem to be consistent with the documented behavior (see below), but this (in docroot/.htaccess, with Apache 2.2.20) works for me:

SetEnvIf REDIRECT_VAR (.+) VAR=$1

Only those environment variables defined by earlier SetEnvIf[NoCase] directives are available for testing in this manner.

Why?

I don't know what the rationale for prefixing these names with REDIRECT_ is -- not surprising, since it doesn't appear to be mentioned in the Apache documentation sections for mod_rewrite directives, RewriteRule flags, or environment variables.

At the moment it seems like a big nuisance to me, in the absence of an explanation for why it's better than leaving the assigned names alone. The lack of documentation only contributes to my skepticism about it.

Being able to assign environment variables in rewrite rules is useful, or at least, it would be. But the usefulness is greatly diminished by this name-changing behavior. The complexity of this post illustrates how nuts this behavior and the hoops that have to be jumped through to try to overcome it are.

夜灵血窟げ 2024-09-12 23:55:47

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

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

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

A: 环境变量由 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;
}

I haven't tested this at all and I know it doesn't address points A or B, but there is some description of this issue in the comments in PHP documentation and some possible solutions for accessing these variables using $_SERVER['VAR']:

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

EDIT - some more responses to the question offered:

A: The environment variables are renamed by Apache if they are involved in a redirect. For example, if you have the following rule:

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

Then you may access VAR1 and VAR2 using $_SERVER['VAR1'] and $_SERVER['VAR2']. However, if you redirect the page like so:

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

Then you must use $_SERVER['REDIRECT_VAR1'], etc.

B: The best way to overcome this issue is to process the variables that you're interested in using PHP. Create a function that runs through the $_SERVER array and finds the items that you need. You might even use a function like this:

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;
}
傾城如夢未必闌珊 2024-09-12 23:55:47

由于我不想更改任何代码(我也不能更改所使用的库的代码),因此我采用了以下方法:同时引导我的应用程序 - 例如在我的索引中.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 中设置它code>$_ENV,但我们也使用 putenv() 存储它。这样,现有的代码和库(可能使用 getenv())就可以正常工作。


旁注:如果您要在代码中提取标头(例如 HTTP_AUTHORIZATION),则需要对 $_SERVER 进行相同类型的操作:

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

As I don't want to change any of my code (nor can I change code of the libraries used), I went with the following approach: whilst bootstrapping my application – e.g. in my index.php – I rework the $_ENV superglobal so that variables prefixed with REDIRECT_ are rewritten to their normal intended name:

// 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);
    }
}

Not only do we directly set it in $_ENV, but we also store it using putenv(). This way existing code and libraries – which might use getenv() – can work fine.


On a sidenote: if you're extracting headers – like HTTP_AUTHORIZATION – in your code, you need to do the same kind of manipulation on $_SERVER:

foreach ($_SERVER as $key => $value) {
    if (substr($key, 0, 9) === 'REDIRECT_') {
        $_SERVER[str_replace('REDIRECT_', '', $key)] = $value;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文