如何在使用通配符的Directory指令中使用apache2 mod_rewrite?

时间:2011-06-15 14:01:39

标签: mod-rewrite apache2

我编写了一个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移出目录会导致查询字符串出现轻微的初始问题从“登录”更改为“/ 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}来解决的。但是,使用%{DOCUMENT_ROOT}在使用VirtualDocumentRoot时不起作用。

我认为%{DOCUMENT_ROOT}前缀不一定是正确的。

修改

  

REQUEST_FILENAME

     

匹配的文件或脚本的完整本地文件系统路径   请求,如果这已经由服务器确定   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]

哪个正常(注意:在使用VirutalDocumentRoot时,必须使用PT标志以避免过早地将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。我的应用程序期望根网址没有q =参数,否则如果应用程序已更新为接受根网址的空RewriteRule参数,则单RewriteRule ^/(.*)$ /index.php?q=$1 [NS,PT,L,QSA] q=就足够了。我可能会在将来这样做。

1 个答案:

答案 0 :(得分:8)

非常好的和详细的问题。

你肯定遇到了一个bug,或者至少是一个没有文档的rewriteRule域。文件说明:

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

因此,没有提及带有通配符的<Directory>指令将无法删除每个目录前缀。使用RewriteBase对你没有帮助,重建最终的Url并不会改变perdir的工作。

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

  • 会更快
  • 它不会受到这个bug的影响
  • 如果某些不存在的文件不应映射到某些其他目录中的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]

它应该有效,让我知道这是否会导致新的问题。

修改

好的,forogot事实上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]