我编写了一个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=
就足够了。我可能会在将来这样做。
答案 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。
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]