使用mod_rewrite时要当心MultiViews

先看我遇到的问题:试图使用如下rewrite代码

RewriteEngine On RewriteBase /dex/ RewriteRule ^wsdl/(sjz|bd|qhd|common)/?$ wsdl.php?u=$1 [L] RewriteRule ^wsdl/?$ wsdl.php [L]

达到以下转换效果:

wsdl/sjz –> wsdl.php?u=sjz wsdl/ –> wsdl.php

但天不随人愿,第二行转换效果没问题,第一行转换效果总是出不来,查了一下RewriteLog,有一句引起了我的注意:

(1) pass through /dex/wsdl/sjz (3) [per-dir E:/cvswork/2005dex/] add path info postfix: E:/cvswork/2005dex/wsdl.php -> E:/cvswork/2005dex/wsdl.php/sjz

注意看第二句,它怎么把wsdl/sjz转换成了wsdl.php/sjz?难道和第二句RewriteRule有关?试着把第二句RewriteRule关掉,问题依旧。

于是祭起Google海搜,发现有人和我遇到了同样的问题

the problem lies in here somewhere with URL http://localhost/index/q we get:- add path-info postfix: c:/wwwroot/index.php -> c:/wwwroot/index.php/q strip per-dir prefix: c:/wwwroot/index.php/q -> index.php/q applying pattern ‘^([^/.]*)$’ to uri ‘index.php/q’ I dont want index.php/q though!!!!! argghhhhh!!! …… GOT IT!!!!!!! Bl**dy windows OS i removed the .htaccess file and http://localhost/phpinfo/x was still doing phpinfo.php I added a directory called phpinfo and it worked – giving a directory listing I re-created the .htaccess and voila it worked. So basically if a directory doesnt exist it looks for a script (.php .html) and then uses that with further parameters sdded to the end of it. I have to find a workaround for that now.

原来,是Apache在当前目录下找到了wsdl.php(依据wsdl/sjz中的wsdl),并且来了个狸猫换太子,继续搜索……终于在这个德语网页找到了答案:

MultiViews-option abschalten. wenn diese aktiviert ist, würde ein request nach /kategorie auch kategorie.php finden, und das kollidiert natürlich mit deiner RewriteRule, weil dort eben mit /kategorie/irgendwas ebenfalls ein “treffer” vorliegt – da kann der server also nicht entscheiden, was jetzt eigentlich gemeint ist.

上面是德语,我是看不懂的,用Google翻译如下:

MultiViews option switch off. if this is activated, after/category kategorie.php to also find request, and collides naturally with your RewriteRule, because evenly with/category/something is likewise present there a “hit” – there the server cannot decide thus, what is now actually meant.

原来是Apache的MultiViews选项干扰了RewriteRule,将其去掉之后,问题排除。

Options -MultiViews

继续寻找MultiViews的相关信息,收获如下:

所以,在没有明确需要的情况下,一般不要打开MultiViews选项,因为我们一般编写的程序很少用到它,反而会给正常的程序调试带来麻烦,另外早期的版本可能还存在一些安全问题。

在去掉MultiViews的同时,发现还有一个好像也没有用的Options——FollowSymLinks,但如果你的RewriteRule是写在.htaccess文件中的话,FollowSymLinks这个选项可不能去掉,去掉了RewriteEngine就不干活啦~~

Note To enable the rewriting engine for per-directory configuration files you need to set “RewriteEngine On” in these files and “Options FollowSymLinks” must be enabled. If your administrator has disabled override of FollowSymLinks for a user’s directory, then you cannot use the rewriting engine. This restriction is needed for security reasons.

Apache自动添加地址末尾的斜线不能用于SVN

一般来说,如果在Apache服务器上建立了一个别名work,然后使用hostname/work来访问的话,会出现404找不到文件的错误,这是因为Apache把work当作一个文件来解析了,一般我们解决这个问题的办法是通过mod_rewrite,自动把后面遗漏掉的/加上,比如:

#自动添加地址末尾的斜线 RewriteEngine on RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^(.+[^/])$ $1/ [R]

另一方面,由于SVN也借助了Apache的DAV方式来进行工作,所以一般使用SVN时会建立一个别名svn,然后使用hostname/svn来进行访问。

上面两个问题加在一起,问题就随之而产生了,在使用TortoiseSVN导入import内容的时候,服务器提示302 Found,但不工作。302 Found意味着要访问的目标已经移动,用户请求将被导向到新的位置。但是似乎TortoiseSVN无法正确处理这样的情况,所以就停留在这里,不工作了。所以,需要修改设置,使得在访问svn目录下的内容的时候,不启用自动添加地址末尾斜线的功能,因此在上面的代码中,在最后的处理RewriteRule之前,加上一句:

RewriteCond %{REQUEST_URI} !/svn*

这样既不影响自动添加地址末尾斜线功能的使用,也不会干扰SVN的正常工作。

另外,Apache 2中有一个mod_dir,并且一般默认都是开启的,已经能够提供自动添加URL末尾斜线的功能了,官方说明如下:

A “trailing slash” redirect is issued when the server receives a request for a URL http://servername/foo/dirname where dirname is a directory. Directories require a trailing slash, so mod_dir issues a redirect to http://servername/foo/dirname/.

所以在一般情况下,上面使用mod_rewrite来添加斜线的设定就不需要了,一个例外的情况就是当你使用了反向代理的时候。

比如设定domain1/dir反向代理到domain2/dir,那么mod_dir就无法正确添加domain1/dir后面的斜线,所以仍然需要使用上面的mod_rewrite功能。

也许,在设定上述反向代理的同时,在domain1上设定alias dir,这样mod_dir就能够正确识别并添加domain1/dir后面的斜线了?我觉得应该是这样的,因为设定了Alias以后,mod_dir就能够识别出domain1/dir是一个目录,从而进行正确的处理了。

经过初步实践,我的设想是正确的,只需把在domain1上设定一个Alias dir,目标随意,就能够让mod_dir正确进行处理了。

为什么要尽量使用mod_dir而不是RewriteRule呢?因为我认为不需要解析httpd.conf的mod_dir应该效率会高些。:-)