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

chr*_*ris 11 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>
Run Code Online (Sandbox Code Playgroud)

但是,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
Run Code Online (Sandbox Code Playgroud)

问题是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>
Run Code Online (Sandbox Code Playgroud)

工作正常,这里是重写日志的输出显示正确的行为(差异是新的第一个额外的行提供正确的输入到其余的重写导致正确的查询字符串: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
Run Code Online (Sandbox Code Playgroud)

我希望我遇到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]
Run Code Online (Sandbox Code Playgroud)

但正如我在对regilero的回答中所说的那样,通过在RewriteCond指令TestString前加上%{DOCUMENT_ROOT}来解决这个问题.但是,使用VirtualDocumentRoot时,使用%{DOCUMENT_ROOT}不起作用.

对我来说,%{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]
Run Code Online (Sandbox Code Playgroud)

哪个工作正常(注意: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]
Run Code Online (Sandbox Code Playgroud)

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

reg*_*ero 8

非常好的和详细的问题.

你肯定遇到过一个bug,或者至少是一个没有文档的rewriteRule域.文件指出:

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

所以没有提到<Directory>带有通配符的事实指令将无法剥离每个目录的前缀.和RewriteBase一起玩也无济于事,重建最终的Url并不会改变perdir的工作.

但正如你在开始时所看到的那样," 有一些额外的复杂性 "句子.通过mod-rewrite完成的目录操作比一般的目录外RewriteRules更慢,更复杂.这也在本文档中说明,主要是因为perdir条带操作.这意味着您还可以<Directory>在VirtualHost 中的部分中编写您的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]
Run Code Online (Sandbox Code Playgroud)

它应该工作,让我知道这是否会导致新的问题.

编辑:

好吧,forogot事实REQUEST_FILENAME尚未complelty在虚拟主机的上下文定义,它的记录,这是" 正常的 ",当条件应用于真实路径上的文件搜索尚未完成,这就是为什么你必须添加的文档根目录.所以实际上你的最终解决方案应该是:

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]
Run Code Online (Sandbox Code Playgroud)

我尝试了第二个,避免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]
Run Code Online (Sandbox Code Playgroud)