【问题标题】:How to use apache2 mod_rewrite within a Directory directive that uses wildcards?如何在使用通配符的目录指令中使用 apache2 mod_rewrite?
【发布时间】:2011-09-15 13:54:24
【问题描述】:

我编写了一个 Web 应用程序,我在专用服务器下运行该 Web 应用程序来托管该 Web 应用程序。此 Web 应用程序的实例在不同的域中可用,并且每个域都有自己的 Web 应用程序文件副本,允许根据需要进行自定义。

我在 Debian Squeeze 下运行 Apache/2.2.16。

我在 VirtualHost 指令下进行所有配置,不使用 .htaccess 文件。

为了简化 apache 配置,我想维护一个这样的 Directory 指令:

<Directory "/srv/www/*/public/">
  RewriteEngine on
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_URI} !=/favicon.ico
  RewriteCond %{REQUEST_URI} !=/robots.txt
  RewriteRule ^(.+)$ /index.php?q=$1 [L,QSA]
</Directory>

但是,RewriteRule 会产生错误的结果,因为在使用通配符 Directory 值时,它无法去除每个目录的前缀。这是重写日志的输出:

[rid#b9832078/initial] (3) [perdir /srv/www/*/public/] applying pattern '^(.+)$' to uri '/srv/www/domain1/public/login'
[rid#b9832078/initial] (4) [perdir /srv/www/*/public/] RewriteCond: input='/srv/www/domain1/public/login' pattern='!-f' => matched
[rid#b9832078/initial] (4) [perdir /srv/www/*/public/] RewriteCond: input='/srv/www/domain1/public/login' pattern='!-d' => matched
[rid#b9832078/initial] (4) [perdir /srv/www/*/public/] RewriteCond: input='/login' pattern='!=/favicon.ico' => matched
[rid#b9832078/initial] (4) [perdir /srv/www/*/public/] RewriteCond: input='/login' pattern='!=/robots.txt' => matched
[rid#b9832078/initial] (2) [perdir /srv/www/*/public/] rewrite '/srv/www/domain1/public/login' -> '/index.php?q=/srv/www/domain1/public/login'
[rid#b9832078/initial] (3) split uri=/index.php?q=/srv/www/domain1/public/login -> uri=/index.php, args=q=/srv/www/domain1/public/login
[rid#b9832078/initial] (1) [perdir /srv/www/*/public/] internal redirect with /index.php [INTERNAL REDIRECT]
[rid#b9847440/initial/redir#1] (3) [perdir /srv/www/*/public/] applying pattern '^(.+)$' to uri '/srv/www/domain1/public/index.php'
[rid#b9847440/initial/redir#1] (4) [perdir /srv/www/*/public/] RewriteCond: input='/srv/www/domain1/public/index.php' pattern='!-f' => not-matched
[rid#b9847440/initial/redir#1] (1) [perdir /srv/www/*/public/] pass through /srv/www/domain1/public/index.php

问题在于 RewriteRule 'uri' 是文件系统路径而不是 url 路径,这导致查询字符串不正确:q=/srv/www/domain1/public/login

像这样显式指定目录路径:

<Directory "/srv/www/domain1/public/">
  RewriteEngine on
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_URI} !=/favicon.ico
  RewriteCond %{REQUEST_URI} !=/robots.txt
  RewriteRule ^(.+)$ /index.php?q=$1 [L,QSA]
</Directory>

工作得很好,这是显示正确行为的重写日志的输出(不同之处在于新的第一行为其余的重写提供了正确的输入,从而产生了正确的查询字符串:q =登录):

[rid#b9868048/initial] (3) [perdir /srv/www/domain1/public/] strip per-dir prefix: /srv/www/domain1/public/login -> login
[rid#b9868048/initial] (3) [perdir /srv/www/domain1/public/] applying pattern '^(.+)$' to uri 'login'
[rid#b9868048/initial] (4) [perdir /srv/www/domain1/public/] RewriteCond: input='/srv/www/domain1/public/login' pattern='!-f' => matched
[rid#b9868048/initial] (4) [perdir /srv/www/domain1/public/] RewriteCond: input='/srv/www/domain1/public/login' pattern='!-d' => matched
[rid#b9868048/initial] (4) [perdir /srv/www/domain1/public/] RewriteCond: input='/login' pattern='!=/favicon.ico' => matched
[rid#b9868048/initial] (4) [perdir /srv/www/domain1/public/] RewriteCond: input='/login' pattern='!=/robots.txt' => matched
[rid#b9868048/initial] (2) [perdir /srv/www/domain1/public/] rewrite 'login' -> '/index.php?q=login'
[rid#b9868048/initial] (3) split uri=/index.php?q=login -> uri=/index.php, args=q=login
[rid#b9868048/initial] (1) [perdir /srv/www/domain1/public/] internal redirect with /index.php [INTERNAL REDIRECT]
[rid#b987d5f8/initial/redir#1] (3) [perdir /srv/www/domain1/public/] strip per-dir prefix: /srv/www/domain1/public/index.php -> index.php
[rid#b987d5f8/initial/redir#1] (3) [perdir /srv/www/domain1/public/] applying pattern '^(.+)$' to uri 'index.php'
[rid#b987d5f8/initial/redir#1] (4) [perdir /srv/www/domain1/public/] RewriteCond: input='/srv/www/domain1/public/index.php' pattern='!-f' => not-matched
[rid#b987d5f8/initial/redir#1] (1) [perdir /srv/www/domain1/public/] pass through /srv/www/domain1/public/index.php

我预计我遇到了 Apache 的错误,但如果不是这样,我做错了什么?

虽然我很欣赏将方法更改为另一种可行解决方案的意见,但我会接受以我采用的方法解决它的答案(例如,不使用 .htaccess),除非可以证明这种方法是不可解决的。

那么,在通配符目录中使用时,RewriteCond/Rules 有什么需要更改的吗?

好奇的旁注:为了进一步简化,我使用单个使用 VirtualDocumentRoot 的 VirtualHost - 但这无关紧要,因为使用“DocumentRoot”和在单个域下测试会复制此问题。

编辑

好的,我已经根据 regilero 的回答重新审视了这个问题,这就是发生的情况 - 将 Rewrite 按原样移出 Directory 会导致查询字符串出现轻微的初始问题从“login”更改为“/login”,通过将 RewriteRule 修改为:RewriteRule ^/(.+)$ /index.php?q=$1 [L,QSA] 修复了我之前的“莫名其妙地失败”评论。

随后,所有静态文件都无法加载,这是显示此问题的重写日志:

[rid#b7bc7fa0/initial] (2) init rewrite engine with requested uri /login
[rid#b7bc7fa0/initial] (3) applying pattern '^/(.+)$' to uri '/login'
[rid#b7bc7fa0/initial] (4) RewriteCond: input='/login' pattern='!-f' => matched
[rid#b7bc7fa0/initial] (4) RewriteCond: input='/login' pattern='!-d' => matched
[rid#b7bc7fa0/initial] (4) RewriteCond: input='/login' pattern='!=/favicon.ico' => matched
[rid#b7bc7fa0/initial] (4) RewriteCond: input='/login' pattern='!=/robots.txt' => matched
[rid#b7bc7fa0/initial] (2) rewrite '/login' -> '/index.php?q=login'
[rid#b7bc7fa0/initial] (3) split uri=/index.php?q=login -> uri=/index.php, args=q=login
[rid#b7bc7fa0/initial] (2) local path result: /index.php
[rid#b7bc7fa0/initial] (2) prefixed with document_root to /srv/www/domain1/public/index.php
[rid#b7bc7fa0/initial] (1) go-ahead with /srv/www/domain1/public/index.php [OK]
[rid#b7be6b80/initial] (2) init rewrite engine with requested uri /static/css/common.css
[rid#b7be6b80/initial] (3) applying pattern '^/(.+)$' to uri '/static/css/common.css'
[rid#b7be6b80/initial] (4) RewriteCond: input='/static/css/common.css' pattern='!-f' => matched
[rid#b7be6b80/initial] (4) RewriteCond: input='/static/css/common.css' pattern='!-d' => matched
[rid#b7be6b80/initial] (4) RewriteCond: input='/static/css/common.css' pattern='!=/favicon.ico' => matched
[rid#b7be6b80/initial] (4) RewriteCond: input='/static/css/common.css' pattern='!=/robots.txt' => matched
[rid#b7be6b80/initial] (2) rewrite '/static/css/common.css' -> '/index.php?q=static/css/common.css'
[rid#b7be6b80/initial] (3) split uri=/index.php?q=static/css/common.css -> uri=/index.php, args=q=static/css/common.css
[rid#b7be6b80/initial] (2) local path result: /index.php
[rid#b7be6b80/initial] (2) prefixed with document_root to /srv/www/domain1/public/index.php
[rid#b7be6b80/initial] (1) go-ahead with /srv/www/domain1/public/index.php [OK]

但就像我在对 regilero 的回答的评论中所说的那样,这可以通过在 RewriteCond 指令 TestString 前加上 %{DOCUMENT_ROOT} 来解决。但是,使用 VirtualDocumentRoot 时,使用 %{DOCUMENT_ROOT} 不起作用。

在我看来,%{DOCUMENT_ROOT} 前缀不是必须的。

编辑

请求文件名

匹配的文件或脚本的完整本地文件系统路径 请求,如果这已经由服务器当时确定 引用了 REQUEST_FILENAME。否则,例如在使用时 虚拟主机上下文,与 REQUEST_URI 的值相同。

这解释了 DOCUMENT_ROOT 前缀的必要性。

我已将重写规则更新为:

RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteCond %{REQUEST_URI} !=/robots.txt
RewriteCond %{REQUEST_URI} !^/static/
RewriteRule ^/(.+)$ /index.php?q=$1 [PT,L,QSA]

哪个工作正常(注意:PT 标志是必要的,以避免在使用 VirutalDocumentRoot 时过早地将 url 路径转换为文件系统路径)。这里的主要行为变化是应用程序的所有入口点都需要 RewriteCond - 类似于 /static 行。

编辑

这是我在任何 Directory 指令之外的 VirtualHost 中的 Rewrite 指令的最终化身:

RewriteEngine on
RewriteCond %{REQUEST_URI} !^/static/
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteCond %{REQUEST_URI} !=/robots.txt
RewriteRule ^/(.+)$ /index.php?q=$1 [NS,PT,L,QSA]
RewriteRule ^/$ /index.php [NS,PT,L,QSA]

我添加了NS 标志以避免额外的内部评估,并添加了第二个RewriteRule 指令以支持使用mod_dir 和DirectoryIndex。我的应用程序不希望根 url 有 q= 参数,否则,如果应用程序更新为接受根 url 的空 q= 参数,则单个 RewriteRuleRewriteRule ^/(.*)$ /index.php?q=$1 [NS,PT,L,QSA] 就足够了。我将来可能会这样做。

【问题讨论】:

    标签: mod-rewrite apache2


    【解决方案1】:

    非常好的和详细的问题。

    您肯定遇到了错误,或者至少遇到了未记录的 rewriteRule 域。文档指出:

    • 重写引擎可用于 .htaccess 文件和 部分,具有一些额外的复杂性。
    • 要在此上下文中启用重写引擎,您需要设置 必须启用“RewriteEngine On”和“Options FollowSymLinks”。如果 您的管理员已禁用对 FollowSymLinks 的覆盖 用户的目录,则不能使用重写引擎。这个限制 出于安全原因需要。
    • 在 .htaccess 文件中使用重写引擎时,每个目录 前缀(对于特定目录始终相同)是自动 为 RewriteRule 模式匹配删除并在之后自动添加 任何相对(不以斜杠或协议名称开头)替换 遇到规则集的结尾。有关更多信息,请参阅 RewriteBase 指令 有关将哪些前缀添加回相对替代的信息。

    所以没有提到带有通配符的&lt;Directory&gt; 指令将无法去除每个目录的前缀。并且玩 RewriteBase 对你没有帮助,它已经完成了重建最终 Url 而不改变 perdir 工作。

    但正如您在开头看到的那样,有“具有一些额外的复杂性”句子。 通过 mod-rewrite 完成的目录操作比一般的目录外 RewriteRules 更慢且更复杂。这在documentation 中也有说明,主要是因为 perdir 条带操作。这意味着您还可以在您的 VirtualHost 中的 &lt;Directory&gt; 部分中编写您的 rewriteRule。

    • 会更快
    • 它不会受到此错误的影响
    • 如果某些不存在的文件不应该映射到其他目录中的index.php?q=$1 规则,它可能会产生一些副作用。但我很确定这对你来说不是问题。

    所以简单地写(不带通配符目录):

    RewriteEngine on
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} !=/favicon.ico
    RewriteCond %{REQUEST_URI} !=/robots.txt
    RewriteRule ^(.+)$ /index.php?q=$1 [L,QSA]
    

    它应该可以工作,如果这会导致新问题,请告诉我。

    编辑:

    好的,忘记了 REQUEST_FILENAME 尚未在 VirtualHost 上下文中完整定义的事实,它已记录在案,它是“正常”,当应用条件时,实际路径上的文件搜索尚未完成,这就是为什么您必须添加文档根目录的原因。所以实际上你的最终解决方案应该是:

    RewriteEngine on
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} !=/favicon.ico
    RewriteCond %{REQUEST_URI} !=/robots.txt
    RewriteRule ^/(.+)$ /index.php?q=$1 [L,QSA]
    

    我尝试了第二个,避免 DOCUMENT_ROOT,使用 REQUEST_FILENAME 的后期评估( %{LA-U:REQUEST_FILENAME} 包含最终路径,实际上是 index.php 的完整路径如果文件不存在),但我让它工作的唯一方法是在第二个中添加第二个规则和一个或条件,不太简单,所以第一个解决方案肯定更好(KISS)。

      RewriteCond %{LA-U:REQUEST_FILENAME} !-f [OR]
      RewriteCond %{LA-U:REQUEST_FILENAME} !/index.php
      RewriteCond %{LA-U:REQUEST_FILENAME} !-d
      RewriteCond %{REQUEST_URI} !=/favicon.ico
      RewriteCond %{REQUEST_URI} !=/robots.txt
      RewriteRule ^/(.+)$ /index.php?q=$1 [L,QSA]
    
      RewriteCond %{LA-U:REQUEST_FILENAME} /index.php
      RewriteRule ^/(.+)$ /index.php?q=$1 [L,QSA]
    

    【讨论】:

    • 它没有,我之前尝试过,它需要在 RewriteCond 前面加上 %{DOCUMENT_ROOT} - 但即便如此,它还是莫名其妙地失败了。我将很快使用该设置重新运行它以提供重写日志输出。此外,使用 %{DOCUMENT_ROOT} 在带有 VirtualDocumentRoot 的 Apache2.2 中不起作用 - 尽管最近几天前已为 Apache2.3 修复了此问题
    • 因此,您可以尝试 mod_macro,而不是 VirtualDocumentRoot 方法,mod_vhost_alias 中引入的所有错误都不存在于 mod_macro 定义的虚拟主机(但您需要每个新用户一行 conf)。
    • 我已经用将重写指令移到目录指令之外的结果更新了我的问题。
    • mod_macro 看起来很有趣 - 但我在这个阶段犹豫是否要加入额外的第 3 方模块 - 如果有必要我会回来的。
    • 接受答案,因为我认为您的陈述“您肯定遇到了一个错误,或者至少是一个未记录的 rewriteRule 域。”回答问题以及您的替代方法满足我的需要。
    猜你喜欢
    • 2012-07-16
    • 2012-09-23
    • 2012-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多