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

问题描述

| 我已经编写了一个Web应用程序,该应用程序在用于承载该Web应用程序的专用服务器下运行。该Web应用程序的实例在不同的域中可用,并且每个域都有Web应用程序文件的自己的副本,从而可以根据需要进行自定义。 我正在Debian Squeeze下运行Apache / 2.2.16。 我在VirtualHost指令下进行所有配置,并且不使用.htaccess文件。 为了简化apache配置,我想维护一个目录指令,例如:
<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 = login):
[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 \”更改为\“ / 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时,为避免过早将url路径转换为文件系统路径,必须使用PT标志)。行为上的主要变化是,应用程序中的所有入口点都需要一个RewriteCond-与/ static行类似。 编辑 这是我在任何目录指令之外的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,QSA]
RewriteRule ^/$ /index.PHP [NS,QSA]
添加
NS
标志以避免进行额外的内部评估,并添加了第二个
RewriteRule
指令以支持使用mod_dir和
DirectoryIndex
。我的应用程序期望根URL没有q =参数,否则,如果将应用程序更新为接受空的根URL
q=
参数,则单个ѭ12的
RewriteRule
就足够了。我将来可能会这样做。     

解决方法

非常好的和详细的问题。 您肯定遇到了一个错误,或者至少遇到了一个未记录的rewriteRule域。文档指出:      重写引擎可用于.htaccess文件和   部分,但有些复杂。   要在这种情况下启用重写引擎,您需要设置   必须启用“ RewriteEngine On”和“ Options FollowSymLinks \”。如果   您的管理员已禁用对FollowSymLinks的覆盖   用户目录,那么您将无法使用重写引擎。这个限制   出于安全原因需要。   在.htaccess文件中使用重写引擎时,每个目录   前缀(对于特定目录始终是相同的)是自动生成的   为RewriteRule模式匹配而删除,并在之后自动添加   任何相对(不是以斜杠或协议名称开头)的替换   遇到规则集的结尾。有关更多信息,请参见RewriteBase指令。   有关将哪些前缀添加回相对替换的信息。    因此,没有提到带有通配符的事实“ 14”指令将无法去除每个目录的前缀。而且使用RewriteBase不会帮助您,它已经完成了重建最终Url的操作,而不会更改perdir的工作。 但是,正如您在一开始所看到的那样,有一句“有一些额外的复杂性”。与一般的目录外RewriteRules相比,由mod-rewrite完成的目录操作更慢且更复杂。本文档中也对此进行了说明,主要是由于perdir条操作。这意味着您还可以在VirtualHost中的
<Directory>
部分之外编写rewriteRule。 会更快 它不会被这个错误击中 如果某些不存在的文件不应该映射到其他目录中的ѭ16规则,则可能会有一些副作用。但我很确定这对您来说不是问题。 因此,只需编写(不使用通配符目录):
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]
它应该工作,如果这导致新的问题,请告诉我。 编辑: 好的,忘记了在VirtualHost上下文中尚未完全定义REQUEST_FILENAME的事实,它被记录为“正常”,当应用该条件时,尚未在真实路径上进行文件搜索,这是为什么必须添加文档根目录。因此,实际上您的最终解决方案应该是:
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的完整路径),但是我使它起作用的唯一方法是在第二个规则中添加第二个Rule和Or条件,这不太简单,因此第一个解决方案肯定更好(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]