高级URL重写指南
本文是mod_rewrite参考文档的补充材料。阐述在实际应用中如何解决网管所面临的基于URL的典型问题,并详细描述了如何配置URL重写规则集以解决这些问题。
结构化的用户主目录
描述:
一些拥有几千个用户的网站通常都使用结构化的用户主目录规划,即每个用户主目录位于一个带 有特定前缀(比如其用户名的第一个字符)的子目录下:/~foo/anypath代表/home/f/foo/.www/anypath,而 /~bar/anypath代表/home/b/bar/.www/anypath
解决方案:
可以使用下列规则集来扩展'~'以达到上述目的。
RewriteEngine on
RewriteRule ^/~(([a-z])[a-z0-9]+)(.*) /home/$2/$1/.www$3
文件系统的重组
描述:
这是一个不加雕琢的例子:一个大量使用目录级规则集以实现平滑的观感,并且从来不用调整数据结 构的杀手级的应用。背景:从1992年开始,net.sw存放了我收集的免费Unix软件包。它是我的爱好也是我的工作,因为在学习计算机科学的同时,业 余时间还做了多年的系统和网络管理员。每周我都需要整理软件,因而建立了一个层次很深的目录结构来存放各种软件包:
drwxrwxr-x 2 netsw users 512 Aug 3 18:39 Audio/
drwxrwxr-x 2 netsw users 512 Jul 9 14:37 Benchmark/
drwxrwxr-x 12 netsw users 512 Jul 9 00:34 Crypto/
drwxrwxr-x 5 netsw users 512 Jul 9 00:41 Database/
drwxrwxr-x 4 netsw users 512 Jul 30 19:25 Dicts/
drwxrwxr-x 10 netsw users 512 Jul 9 01:54 Graphic/
drwxrwxr-x 5 netsw users 512 Jul 9 01:58 Hackers/
drwxrwxr-x 8 netsw users 512 Jul 9 03:19 InfoSys/
drwxrwxr-x 3 netsw users 512 Jul 9 03:21 Math/
drwxrwxr-x 3 netsw users 512 Jul 9 03:24 Misc/
drwxrwxr-x 9 netsw users 512 Aug 1 16:33 Network/
drwxrwxr-x 2 netsw users 512 Jul 9 05:53 Office/
drwxrwxr-x 7 netsw users 512 Jul 9 09:24 SoftEng/
drwxrwxr-x 7 netsw users 512 Jul 9 12:17 System/
drwxrwxr-x 12 netsw users 512 Aug 3 20:15 Typesetting/
drwxrwxr-x 10 netsw users 512 Jul 9 14:08 X11/
内容处理
依赖于浏览器的内容
描述:
有时候有必须提供依赖于浏览器的最佳内容(至少对重要的顶级页面),即对最新的Netscape提供最大化的版本,对Lynx提供最小化的版本,而对其他的浏览器则提供一个一般的版本。
解决方案:
对 此,内容协商无能为力,因为浏览器不提供那种形式的类型,所以只能在"User-Agent"头上想办法。以下规则集可以完成这个操作:如果"User- Agent"以"Mozilla/3"开头,则将foo.html重写为foo.NS.html,并终止重写操作;如果是"Lynx"或者版本号为1和2 的"Mozilla",则重写为foo.20.html;而对其他所有浏览器则是foo.32.html。
RewriteCond %{HTTP_USER_AGENT} ^Mozilla/3.*
RewriteRule ^foo\.html$ foo.NS.html [L]
访问控制
对主机的拒绝
描述:
如何禁止一批外部列表中的主机对我们服务器的访问?
解决方案:
RewriteEngine on
RewriteMap hosts-deny txt:/path/to/hosts.deny
RewriteCond ${hosts-deny:%{REMOTE_HOST}|NOT-FOUND} !=NOT-FOUND [OR]
RewriteCond ${hosts-deny:%{REMOTE_ADDR}|NOT-FOUND} !=NOT-FOUND
RewriteRule ^/.* - [F]
本文是mod_rewrite参考文档的补充材料。阐述在实际应用中如何解决网管所面临的基于URL的典型问题,并详细描述了如何配置URL重写规则集以解决这些问题。
注意:根据你的服务器配置,有可能必须对这里的例子作些小修改,比如,在额外启用mod_alias和mod_userdir的情况下要增加
[PT]标志,或者为了适应目录级(.htaccess)的配置而将针对服务器级的规则集进行重写。对一个特定的规则集应该先透彻理解然后再考虑应用,这
样才能避免出现问题。
集群网站的同类URL规划
描述:
我们希望在一个Intranet集群网站中,对所有WWW服务器建立一致的URL规划,也就 是说,所有的URL(针对每个服务器进行本地配置,因此是独立于各个服务器的)实际上都是独立于各个服务器的!我们需要的是一个具有独立于各个服务器的一 致性的WWW域名空间,即URL不需要包含物理目标服务器,而由集群本身来自动定位物理目标主机。
解决方案:
首先,目标服务器的信息来自于(分布式的)包含有用户、组以及实体的外部映射表,其格式形如下:
user1 server_of_user1
user2 server_of_user2
: :
描述:
我们希望在一个Intranet集群网站中,对所有WWW服务器建立一致的URL规划,也就 是说,所有的URL(针对每个服务器进行本地配置,因此是独立于各个服务器的)实际上都是独立于各个服务器的!我们需要的是一个具有独立于各个服务器的一 致性的WWW域名空间,即URL不需要包含物理目标服务器,而由集群本身来自动定位物理目标主机。
解决方案:
首先,目标服务器的信息来自于(分布式的)包含有用户、组以及实体的外部映射表,其格式形如下:
user1 server_of_user1
user2 server_of_user2
: :
这些信息被存入map.xxx-to-host文件。其次,如果URL在一个服务器上无效,我们还需要引导所有的服务器将
/u/user/anypath
/g/group/anypath
/e/entity/anypath
/u/user/anypath
/g/group/anypath
/e/entity/anypath
重定向到
http://physical-host/u/user/anypath
http://physical-host/g/group/anypath
http://physical-host/e/entity/anypath
http://physical-host/u/user/anypath
http://physical-host/g/group/anypath
http://physical-host/e/entity/anypath
以下规则集依靠映射文件来完成这个操作(如果一个用户在映射表中没有对应的项,则使用server0做为默认服务器):
RewriteEngine on
RewriteEngine on
RewriteMap user-to-host txt:/path/to/map.user-to-host
RewriteMap group-to-host txt:/path/to/map.group-to-host
RewriteMap entity-to-host txt:/path/to/map.entity-to-host
RewriteMap group-to-host txt:/path/to/map.group-to-host
RewriteMap entity-to-host txt:/path/to/map.entity-to-host
RewriteRule ^/u/([^/]+)/?(.*) http://${user-to-host:$1|server0}/u/$1/$2
RewriteRule ^/g/([^/]+)/?(.*) http://${group-to-host:$1|server0}/g/$1/$2
RewriteRule ^/e/([^/]+)/?(.*) http://${entity-to-host:$1|server0}/e/$1/$2
RewriteRule ^/g/([^/]+)/?(.*) http://${group-to-host:$1|server0}/g/$1/$2
RewriteRule ^/e/([^/]+)/?(.*) http://${entity-to-host:$1|server0}/e/$1/$2
RewriteRule ^/([uge])/([^/]+)/?$ /$1/$2/.www/
RewriteRule ^/([uge])/([^/]+)/([^.]+.+) /$1/$2/.www/$3\
RewriteRule ^/([uge])/([^/]+)/([^.]+.+) /$1/$2/.www/$3\
结构化的用户主目录
描述:
一些拥有几千个用户的网站通常都使用结构化的用户主目录规划,即每个用户主目录位于一个带 有特定前缀(比如其用户名的第一个字符)的子目录下:/~foo/anypath代表/home/f/foo/.www/anypath,而 /~bar/anypath代表/home/b/bar/.www/anypath
解决方案:
可以使用下列规则集来扩展'~'以达到上述目的。
RewriteEngine on
RewriteRule ^/~(([a-z])[a-z0-9]+)(.*) /home/$2/$1/.www$3
文件系统的重组
描述:
这是一个不加雕琢的例子:一个大量使用目录级规则集以实现平滑的观感,并且从来不用调整数据结 构的杀手级的应用。背景:从1992年开始,net.sw存放了我收集的免费Unix软件包。它是我的爱好也是我的工作,因为在学习计算机科学的同时,业 余时间还做了多年的系统和网络管理员。每周我都需要整理软件,因而建立了一个层次很深的目录结构来存放各种软件包:
drwxrwxr-x 2 netsw users 512 Aug 3 18:39 Audio/
drwxrwxr-x 2 netsw users 512 Jul 9 14:37 Benchmark/
drwxrwxr-x 12 netsw users 512 Jul 9 00:34 Crypto/
drwxrwxr-x 5 netsw users 512 Jul 9 00:41 Database/
drwxrwxr-x 4 netsw users 512 Jul 30 19:25 Dicts/
drwxrwxr-x 10 netsw users 512 Jul 9 01:54 Graphic/
drwxrwxr-x 5 netsw users 512 Jul 9 01:58 Hackers/
drwxrwxr-x 8 netsw users 512 Jul 9 03:19 InfoSys/
drwxrwxr-x 3 netsw users 512 Jul 9 03:21 Math/
drwxrwxr-x 3 netsw users 512 Jul 9 03:24 Misc/
drwxrwxr-x 9 netsw users 512 Aug 1 16:33 Network/
drwxrwxr-x 2 netsw users 512 Jul 9 05:53 Office/
drwxrwxr-x 7 netsw users 512 Jul 9 09:24 SoftEng/
drwxrwxr-x 7 netsw users 512 Jul 9 12:17 System/
drwxrwxr-x 12 netsw users 512 Aug 3 20:15 Typesetting/
drwxrwxr-x 10 netsw users 512 Jul 9 14:08 X11/
1996年7月,我决定通过一个漂亮的Web接口公开我的收藏。"漂亮"是指提供一个直接浏览整个目录结构的接口,同时不对这个结构做任何改
变,甚至也不在结构顶部放置CGI脚本。为什么呢?因为这个结构还要能够被FTP访问,而且我不希望其中有任何Web或者CGI成分。
解决方案:
这个方案分为两个部分:第一个部分是一组CGI脚本,用于实时建立所有目录层次的页面。我把它们放在/e/netsw/.www/中:
-rw-r--r-- 1 netsw users 1318 Aug 1 18:10 .wwwacl
drwxr-xr-x 18 netsw users 512 Aug 5 15:51 DATA/
-rw-rw-rw- 1 netsw users 372982 Aug 5 16:35 LOGFILE
-rw-r--r-- 1 netsw users 659 Aug 4 09:27 TODO
-rw-r--r-- 1 netsw users 5697 Aug 1 18:01 netsw-about.html
-rwxr-xr-x 1 netsw users 579 Aug 2 10:33 netsw-access.pl
-rwxr-xr-x 1 netsw users 1532 Aug 1 17:35 netsw-changes.cgi
-rwxr-xr-x 1 netsw users 2866 Aug 5 14:49 netsw-home.cgi
drwxr-xr-x 2 netsw users 512 Jul 8 23:47 netsw-img/
-rwxr-xr-x 1 netsw users 24050 Aug 5 15:49 netsw-lsdir.cgi
-rwxr-xr-x 1 netsw users 1589 Aug 3 18:43 netsw-search.cgi
-rwxr-xr-x 1 netsw users 1885 Aug 1 17:41 netsw-tree.cgi
-rw-r--r-- 1 netsw users 234 Jul 30 16:35 netsw-unlimit.lst
解决方案:
这个方案分为两个部分:第一个部分是一组CGI脚本,用于实时建立所有目录层次的页面。我把它们放在/e/netsw/.www/中:
-rw-r--r-- 1 netsw users 1318 Aug 1 18:10 .wwwacl
drwxr-xr-x 18 netsw users 512 Aug 5 15:51 DATA/
-rw-rw-rw- 1 netsw users 372982 Aug 5 16:35 LOGFILE
-rw-r--r-- 1 netsw users 659 Aug 4 09:27 TODO
-rw-r--r-- 1 netsw users 5697 Aug 1 18:01 netsw-about.html
-rwxr-xr-x 1 netsw users 579 Aug 2 10:33 netsw-access.pl
-rwxr-xr-x 1 netsw users 1532 Aug 1 17:35 netsw-changes.cgi
-rwxr-xr-x 1 netsw users 2866 Aug 5 14:49 netsw-home.cgi
drwxr-xr-x 2 netsw users 512 Jul 8 23:47 netsw-img/
-rwxr-xr-x 1 netsw users 24050 Aug 5 15:49 netsw-lsdir.cgi
-rwxr-xr-x 1 netsw users 1589 Aug 3 18:43 netsw-search.cgi
-rwxr-xr-x 1 netsw users 1885 Aug 1 17:41 netsw-tree.cgi
-rw-r--r-- 1 netsw users 234 Jul 30 16:35 netsw-unlimit.lst
其中的DATA/子目录包含了上述目录结构,即实际的net.sw原始内容,由rdist在需要的时候自动更新。第二个部分的问题是:如何将这
两个结构连接为一个观感平滑的URL树?我希望在为各种URL运行对应的CGI脚本的时候,用户感觉不到DATA/目录的存在。方案如下:首先,我把下列
配置放在针对每个目录的配置文件里,将公布的URL"/net.sw/"重写为内部路径"/e/netsw":
RewriteRule ^net.sw$ net.sw/ [R]
RewriteRule ^net.sw/(.*)$ e/netsw/$1
RewriteRule ^net.sw$ net.sw/ [R]
RewriteRule ^net.sw/(.*)$ e/netsw/$1
第一条规则是针对遗漏后缀斜杠的请求的!第二条规则才是真正实现功能的。接着,就是放在目录级配置文件/e/netsw/.www/.wwwacl中的杀手级的配置了:
Options ExecCGI FollowSymLinks Includes MultiViews
Options ExecCGI FollowSymLinks Includes MultiViews
RewriteEngine on
# 通过"/net.sw/"前缀到达
RewriteBase /net.sw/
RewriteBase /net.sw/
# 首先将根目录重写到cgi处理脚本
RewriteRule ^$ netsw-home.cgi [L]
RewriteRule ^index\.html$ netsw-home.cgi [L]
RewriteRule ^$ netsw-home.cgi [L]
RewriteRule ^index\.html$ netsw-home.cgi [L]
# 当浏览器请求perdir页面时剥去子目录
RewriteRule ^.+/(netsw-[^/]+/.+)$ $1 [L]
RewriteRule ^.+/(netsw-[^/]+/.+)$ $1 [L]
# 现在打断对本地文件的重写
RewriteRule ^netsw-home\.cgi.* - [L]
RewriteRule ^netsw-changes\.cgi.* - [L]
RewriteRule ^netsw-search\.cgi.* - [L]
RewriteRule ^netsw-tree\.cgi$ - [L]
RewriteRule ^netsw-about\.html$ - [L]
RewriteRule ^netsw-img/.*$ - [L]
RewriteRule ^netsw-home\.cgi.* - [L]
RewriteRule ^netsw-changes\.cgi.* - [L]
RewriteRule ^netsw-search\.cgi.* - [L]
RewriteRule ^netsw-tree\.cgi$ - [L]
RewriteRule ^netsw-about\.html$ - [L]
RewriteRule ^netsw-img/.*$ - [L]
# 任何别的东西都是一个由另一个cgi脚本处理的子目录
RewriteRule !^netsw-lsdir\.cgi.* - [C]
RewriteRule (.*) netsw-lsdir.cgi/$1
RewriteRule !^netsw-lsdir\.cgi.* - [C]
RewriteRule (.*) netsw-lsdir.cgi/$1
阅读提示:
注意前半部分中的L(最后)标志和非替换部分('-')
注意后半部分中的!(非)符号和C(链)标志
注意最后一条规则是全匹配模式
注意后半部分中的!(非)符号和C(链)标志
注意最后一条规则是全匹配模式
将失败的URL重定向到其他web服务器
描述:
一个常见的问题是如何将对web服务器A的失败请求重定向到服务器B。一般,可以使用借助ErrorDocument的CGI脚本来解决,此外,还有使用mod_rewrite的方案。但是须注意,这种方法的执行效率不如使用ErrorDocument的CGI脚本!
解决方案:
第一种方案,有最好的性能而灵活性欠佳,出错概率小所以安全:
RewriteEngine on
RewriteCond /your/docroot/%{REQUEST_FILENAME} !-f
RewriteRule ^(.+) http://webserverB.dom/$1
描述:
一个常见的问题是如何将对web服务器A的失败请求重定向到服务器B。一般,可以使用借助ErrorDocument的CGI脚本来解决,此外,还有使用mod_rewrite的方案。但是须注意,这种方法的执行效率不如使用ErrorDocument的CGI脚本!
解决方案:
第一种方案,有最好的性能而灵活性欠佳,出错概率小所以安全:
RewriteEngine on
RewriteCond /your/docroot/%{REQUEST_FILENAME} !-f
RewriteRule ^(.+) http://webserverB.dom/$1
但是问题在于,它只对位于DocumentRoot中的页面有效。虽然可以增加更多的条件(比如同时还处理用户主目录,等等),但是还有一个更好的方法:
RewriteEngine on
RewriteCond %{REQUEST_URI} !-U
RewriteRule ^(.+) http://webserverB.dom/$1
RewriteEngine on
RewriteCond %{REQUEST_URI} !-U
RewriteRule ^(.+) http://webserverB.dom/$1
这种方法使用了mod_rewrite提供的"前瞻"(look-ahead)的功能,是一种对所有URL类型都有效而且安全的方法,但是对
web服务器的性能有不利影响。如果web服务器有一个强大的CPU,那就用这个方法。而在慢速机器上,可以用第一种方法,或者用性能更好的CGI脚本。
文档访问的多路复用
描述:
你知道http://www.perl.com/CPAN的CPAN(综合Perl存档网络)吗?它实现了一个重定向以提供全世界的CPAN镜像中离访问者最近的FTP站点,也可以称之为FTP访问多路复用服务。CPAN是通过CGI脚本实现的,那么用mod_rewrite如何实现呢?
解决方案:
首先,mod_rewrite从3.0.0版本开始可以重写"ftp:"类型。其次,可以用RewriteMap取得对客户端顶级域名的最短路径。利用链式规则集,并用顶级域名作为查找多路复用映射表的键,可以这样做:
RewriteEngine on
RewriteMap multiplex txt:/path/to/map.cxan
RewriteRule ^/CxAN/(.*) %{REMOTE_HOST}::$1 [C]
RewriteRule ^.+\.([a-zA-Z]+)::(.*)$ ${multiplex:$1|ftp.default.dom}$2 [R,L]
描述:
你知道http://www.perl.com/CPAN的CPAN(综合Perl存档网络)吗?它实现了一个重定向以提供全世界的CPAN镜像中离访问者最近的FTP站点,也可以称之为FTP访问多路复用服务。CPAN是通过CGI脚本实现的,那么用mod_rewrite如何实现呢?
解决方案:
首先,mod_rewrite从3.0.0版本开始可以重写"ftp:"类型。其次,可以用RewriteMap取得对客户端顶级域名的最短路径。利用链式规则集,并用顶级域名作为查找多路复用映射表的键,可以这样做:
RewriteEngine on
RewriteMap multiplex txt:/path/to/map.cxan
RewriteRule ^/CxAN/(.*) %{REMOTE_HOST}::$1 [C]
RewriteRule ^.+\.([a-zA-Z]+)::(.*)$ ${multiplex:$1|ftp.default.dom}$2 [R,L]
##
## map.cxan -- CxAN多路映射表
##
## map.cxan -- CxAN多路映射表
##
内容处理
依赖于浏览器的内容
描述:
有时候有必须提供依赖于浏览器的最佳内容(至少对重要的顶级页面),即对最新的Netscape提供最大化的版本,对Lynx提供最小化的版本,而对其他的浏览器则提供一个一般的版本。
解决方案:
对 此,内容协商无能为力,因为浏览器不提供那种形式的类型,所以只能在"User-Agent"头上想办法。以下规则集可以完成这个操作:如果"User- Agent"以"Mozilla/3"开头,则将foo.html重写为foo.NS.html,并终止重写操作;如果是"Lynx"或者版本号为1和2 的"Mozilla",则重写为foo.20.html;而对其他所有浏览器则是foo.32.html。
RewriteCond %{HTTP_USER_AGENT} ^Mozilla/3.*
RewriteRule ^foo\.html$ foo.NS.html [L]
RewriteCond %{HTTP_USER_AGENT} ^Lynx/.* [OR]
RewriteCond %{HTTP_USER_AGENT} ^Mozilla/[12].*
RewriteRule ^foo\.html$ foo.20.html [L]
RewriteCond %{HTTP_USER_AGENT} ^Mozilla/[12].*
RewriteRule ^foo\.html$ foo.20.html [L]
RewriteRule ^foo\.html$ foo.32.html [L]
动态镜像
描述:
假定,需要在我们的域名空间里加入其他远程主机的页面。对FTP服务器,可以用mirror程序在本地机器上 维持一个对远程数据的最新的拷贝;对HTTP服务器,可以使用webcopy程序。但这两种技术都有一个主要的缺点:本地拷贝必须通过这个程序来更新。所 以,比较好的方法是不采用静态镜像,而采用动态镜像,即在有数据请求时自动更新(远程主机上更新的数据)。
解决方案:
为达到此目的,可以使用代理吞吐(Proxy Throughput)功能([P]标志),将远程页面甚至整个远程网络区域映射到我们的域名空间:
RewriteEngine on
RewriteBase /~quux/
RewriteRule ^hotsheet/(.*)$ http://www.tstimpreso.com/hotsheet/$1 [P]
描述:
假定,需要在我们的域名空间里加入其他远程主机的页面。对FTP服务器,可以用mirror程序在本地机器上 维持一个对远程数据的最新的拷贝;对HTTP服务器,可以使用webcopy程序。但这两种技术都有一个主要的缺点:本地拷贝必须通过这个程序来更新。所 以,比较好的方法是不采用静态镜像,而采用动态镜像,即在有数据请求时自动更新(远程主机上更新的数据)。
解决方案:
为达到此目的,可以使用代理吞吐(Proxy Throughput)功能([P]标志),将远程页面甚至整个远程网络区域映射到我们的域名空间:
RewriteEngine on
RewriteBase /~quux/
RewriteRule ^hotsheet/(.*)$ http://www.tstimpreso.com/hotsheet/$1 [P]
RewriteEngine on
RewriteBase /~quux/
RewriteRule ^usa-news\.html$ http://www.quux-corp.com/news/index.html [P]
RewriteBase /~quux/
RewriteRule ^usa-news\.html$ http://www.quux-corp.com/news/index.html [P]
反向动态镜像
描述:
...
解决方案:
RewriteEngine on
RewriteCond /mirror/of/remotesite/$1 -U
RewriteRule ^http://www\.remotesite\.com/(.*)$ /mirror/of/remotesite/$1
描述:
...
解决方案:
RewriteEngine on
RewriteCond /mirror/of/remotesite/$1 -U
RewriteRule ^http://www\.remotesite\.com/(.*)$ /mirror/of/remotesite/$1
通过Intranet取得丢失的数据
描述:
这在受防火墙保护的内部网(www2.quux-corp.dom)上保存和维护实际数据,而在Internet上虚拟地运行web服务器(www.quux-corp.dom)。方法是外部服务器在空闲时间从内部服务器取得被请求的数据。
解决方案:
首先,必须确保防火墙对内部服务器的保护,并只允许此外部服务器获取数据。对包过滤(packet-filtering)防火墙,可以制定如下防火墙规则:
ALLOW Host www.quux-corp.dom Port >1024 --> Host www2.quux-corp.dom Port 80
DENY Host * Port * --> Host www2.quux-corp.dom Port 80
描述:
这在受防火墙保护的内部网(www2.quux-corp.dom)上保存和维护实际数据,而在Internet上虚拟地运行web服务器(www.quux-corp.dom)。方法是外部服务器在空闲时间从内部服务器取得被请求的数据。
解决方案:
首先,必须确保防火墙对内部服务器的保护,并只允许此外部服务器获取数据。对包过滤(packet-filtering)防火墙,可以制定如下防火墙规则:
ALLOW Host www.quux-corp.dom Port >1024 --> Host www2.quux-corp.dom Port 80
DENY Host * Port * --> Host www2.quux-corp.dom Port 80
请按你的实际情况,对上例稍作调整。接着,建立通过代理后台获取丢失数据的重写规则:
RewriteRule ^/~([^/]+)/?(.*) /home/$1/.www/$2
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^/home/([^/]+)/.www/?(.*) http://www2.quux-corp.dom/~$1/pub/$2 [P]
RewriteRule ^/~([^/]+)/?(.*) /home/$1/.www/$2
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^/home/([^/]+)/.www/?(.*) http://www2.quux-corp.dom/~$1/pub/$2 [P]
负载均衡
描述:
如何将www.foo.com的负载均衡到www[0-5].foo.com(一共是6个服务器)?
解决方案:
这个问题有许多可能的解决方案,在此,我们讨论通称为"基于DNS"的方案,和特殊的使用mod_rewrite的方案
DNS循环
最简单的方法是用BIND的DNS循环特性,只要按惯例设置www[0-9].foo.com的DNS的A(地址)记录,如:
描述:
如何将www.foo.com的负载均衡到www[0-5].foo.com(一共是6个服务器)?
解决方案:
这个问题有许多可能的解决方案,在此,我们讨论通称为"基于DNS"的方案,和特殊的使用mod_rewrite的方案
DNS循环
最简单的方法是用BIND的DNS循环特性,只要按惯例设置www[0-9].foo.com的DNS的A(地址)记录,如:
www0 IN A 1.2.3.1
www1 IN A 1.2.3.2
www2 IN A 1.2.3.3
www3 IN A 1.2.3.4
www4 IN A 1.2.3.5
www5 IN A 1.2.3.6
www1 IN A 1.2.3.2
www2 IN A 1.2.3.3
www3 IN A 1.2.3.4
www4 IN A 1.2.3.5
www5 IN A 1.2.3.6
然后,增加以下各项:
www IN CNAME www0.foo.com.
IN CNAME www1.foo.com.
IN CNAME www2.foo.com.
IN CNAME www3.foo.com.
IN CNAME www4.foo.com.
IN CNAME www5.foo.com.
IN CNAME www6.foo.com.
www IN CNAME www0.foo.com.
IN CNAME www1.foo.com.
IN CNAME www2.foo.com.
IN CNAME www3.foo.com.
IN CNAME www4.foo.com.
IN CNAME www5.foo.com.
IN CNAME www6.foo.com.
注意,上述看起来似乎是错误的,但事实上,它的确是BIND中一个特意的特性,而且也可以这样用。在解析www.foo.com时,BIND将给出www0-www6的结果(虽然每次在次序上会有轻微的置换/循环),客户端的请求可以被分散到各个服务器。但这并不是一个优秀的负载均衡方案,因为DNS解析信息可以被网络中其他域名服务器缓冲,一旦www.foo.com被解析为wwwN.foo.com,该请求的所有后继请求都将被送往wwwN.foo.com。但是最终结果是正确的,因为请求的总量的确被分散到各个服务器了。
DNS负载均衡
一种成熟的基于DNS的负载均衡方法是使用lbnamed程序,它是一个Perl程序,并带有若干辅助工具,实现了真正的基于DNS的负载均衡。
DNS负载均衡
一种成熟的基于DNS的负载均衡方法是使用lbnamed程序,它是一个Perl程序,并带有若干辅助工具,实现了真正的基于DNS的负载均衡。
代理吞吐循环(Proxy Throughput Round-Robin)
这是一个使用mod_rewrite以及代理吞吐特性的方法。首先,在DNS记录中将www0.foo.com固定为www.foo.com,如下:
这是一个使用mod_rewrite以及代理吞吐特性的方法。首先,在DNS记录中将www0.foo.com固定为www.foo.com,如下:
www IN CNAME www0.foo.com.
其次,将www0.foo.com转换为一个专职代理服务器,即由这个机器把所有到来的URL通过内部代理分散到另外5个服务器(www1-www5)。为此,必须建立一个规则集,对所有URL调用一个负载均衡脚本lb.pl 。
RewriteEngine on
RewriteMap lb prg:/path/to/lb.pl
RewriteRule ^/(.+)$ ${lb:$1} [P,L]
RewriteMap lb prg:/path/to/lb.pl
RewriteRule ^/(.+)$ ${lb:$1} [P,L]
下面是lb.pl的内容:
#!/path/to/perl
## lb.pl -- 负载平衡脚本
#!/path/to/perl
## lb.pl -- 负载平衡脚本
$| = 1;
$name = "www"; # the hostname base
$first = 1; # the first server (not 0 here, because 0 is myself)
$last = 5; # the last server in the round-robin
$domain = "foo.dom"; # the domainname
$first = 1; # the first server (not 0 here, because 0 is myself)
$last = 5; # the last server in the round-robin
$domain = "foo.dom"; # the domainname
$cnt = 0;
while (<STDIN>) {
$cnt = (($cnt+1) % ($last+1-$first));
$server = sprintf("%s%d.%s", $name, $cnt+$first, $domain);
print "http://$server/$_";
}
while (<STDIN>) {
$cnt = (($cnt+1) % ($last+1-$first));
$server = sprintf("%s%d.%s", $name, $cnt+$first, $domain);
print "http://$server/$_";
}
##EOF##
最后的说明:这样有用吗?似乎www0.foo.com也会超载呀?答案是:没错,它的确会超载,但是它超载的仅仅是简单的代理吞吐请求!所有诸如SSI、CGI、ePerl等的处理完全是由其他机器完成的,这个才是重点。
硬件/TCP循环
还有一个硬件解决方案。Cisco有一个叫LocalDirector的东西,实现了TCP/IP层的负载均衡,事实上,它是一个位于网站集群前端的电路级网关。如果你有足够资金而且的确需要高性能的解决方案,那么可以用这个。
硬件/TCP循环
还有一个硬件解决方案。Cisco有一个叫LocalDirector的东西,实现了TCP/IP层的负载均衡,事实上,它是一个位于网站集群前端的电路级网关。如果你有足够资金而且的确需要高性能的解决方案,那么可以用这个。
新的MIME类型,新的服务
描述:
网上有许多很巧妙的CGI程序,但是用法晦涩,许多网管弃之不用。即使是Apache的 MEME类型的动作处理器,也仅仅在CGI程序不需要在其输入中包含PATH_INFO和QUERY_STRINGS时才很好用。首先,配置一种新的后缀 为.scgi的(安全CGI)文件类型,其处理器是很常见的cgiwrap程序。问题是:如果使用同类URL规划(见上述),而用户宿主目录中的一个文件 的URL是/u/user/foo/bar.scgi,可是cgiwrap要求的URL的格式是/~user/foo/bar.scgi/,以下重写规则 解决了这个问题:
RewriteRule ^/[uge]/([^/]+)/\.www/(.+)\.scgi(.*) ...
... /internal/cgi/user/cgiwrap/~$1/$2.scgi$3 [NS,T=application/x-http-cgi]
描述:
网上有许多很巧妙的CGI程序,但是用法晦涩,许多网管弃之不用。即使是Apache的 MEME类型的动作处理器,也仅仅在CGI程序不需要在其输入中包含PATH_INFO和QUERY_STRINGS时才很好用。首先,配置一种新的后缀 为.scgi的(安全CGI)文件类型,其处理器是很常见的cgiwrap程序。问题是:如果使用同类URL规划(见上述),而用户宿主目录中的一个文件 的URL是/u/user/foo/bar.scgi,可是cgiwrap要求的URL的格式是/~user/foo/bar.scgi/,以下重写规则 解决了这个问题:
RewriteRule ^/[uge]/([^/]+)/\.www/(.+)\.scgi(.*) ...
... /internal/cgi/user/cgiwrap/~$1/$2.scgi$3 [NS,T=application/x-http-cgi]
另外,假设还需要使用其他程序:wwwlog(显示access.log中的一个URL子树)和wwwidx(对一个URL子树运行
Glimpse),则必须为两个程序提供URL区域作为其操作对象。比如,对/u/user/foo/执行swwidx程序的超链是这样的:
/internal/cgi/user/swwidx?i=/u/user/foo/
/internal/cgi/user/swwidx?i=/u/user/foo/
其缺点是,必须同时硬编码超链中的区域和CGI的路径,如果重组了这个区域,就需要花费大量时间来修改各个超链。
解决方案:
用一个特殊的新的URL格式,自动拼装CGI参数:
RewriteRule ^/([uge])/([^/]+)(/?.*)/\* /internal/cgi/user/wwwidx?i=/$1/$2$3/
RewriteRule ^/([uge])/([^/]+)(/?.*):log /internal/cgi/user/wwwlog?f=/$1/$2$3
解决方案:
用一个特殊的新的URL格式,自动拼装CGI参数:
RewriteRule ^/([uge])/([^/]+)(/?.*)/\* /internal/cgi/user/wwwidx?i=/$1/$2$3/
RewriteRule ^/([uge])/([^/]+)(/?.*):log /internal/cgi/user/wwwlog?f=/$1/$2$3
现在,这个搜索/u/user/foo/的超链简化成了:
HREF="*"
HREF="*"
它会在内部被自动转换为
/internal/cgi/user/wwwidx?i=/u/user/foo/
/internal/cgi/user/wwwidx?i=/u/user/foo/
这样就可以为使用":log"的超链拼装出调用CGI程序的参数。
传输过程中的内容协商
描述:
这是一个很深奥的功能:动态地生成但静态地发送(从文件系统中读出,然后直接发出去),但是如果它丢失了,则由服务器动态生成。这样,可以静态地提供CGI生成的页面,除非有人(或者是一个计划任务)删除了这些静态页面,而且其内容可以得到更新。
解决方案:
以下规则集实现了这个功能:
RewriteCond %{REQUEST_FILENAME} !-s
RewriteRule ^page\.html$ page.cgi [T=application/x-httpd-cgi,L]
传输过程中的内容协商
描述:
这是一个很深奥的功能:动态地生成但静态地发送(从文件系统中读出,然后直接发出去),但是如果它丢失了,则由服务器动态生成。这样,可以静态地提供CGI生成的页面,除非有人(或者是一个计划任务)删除了这些静态页面,而且其内容可以得到更新。
解决方案:
以下规则集实现了这个功能:
RewriteCond %{REQUEST_FILENAME} !-s
RewriteRule ^page\.html$ page.cgi [T=application/x-httpd-cgi,L]
这样,如果page.html不存在或者文件大小为null
,则对page.html的请求会导致page.cgi的运行。其中奥妙在于page.cgi是一个将输出写入到page.html的(同时也写入
STDOUT)的CGI脚本。执行完毕,服务器则将page.html的内容发送出去。如果网管需要强制更新其内容,只须删除page.html即可(通
常由一个计划任务完成)。
自动更新的文档
描述:
建立一个复杂的页面,能够在用编辑器写了一个更新的版本时自动在浏览器上得到刷新,这不是很好吗?这可能吗?
解决方案:
这是可行的!这需要综合利用MIME多成分、web服务器的NPH和mod_rewrite的URL操控特性。首先,建立一个新的URL特性:对在文件系统中需要刷新的所有URL加上":refresh" 。
RewriteRule ^(/[uge]/[^/]+/?.*):refresh /internal/cgi/apache/nph-refresh?f=$1
自动更新的文档
描述:
建立一个复杂的页面,能够在用编辑器写了一个更新的版本时自动在浏览器上得到刷新,这不是很好吗?这可能吗?
解决方案:
这是可行的!这需要综合利用MIME多成分、web服务器的NPH和mod_rewrite的URL操控特性。首先,建立一个新的URL特性:对在文件系统中需要刷新的所有URL加上":refresh" 。
RewriteRule ^(/[uge]/[^/]+/?.*):refresh /internal/cgi/apache/nph-refresh?f=$1
现在当我们引用如下URL
/u/foo/bar/page.html:refresh
/u/foo/bar/page.html:refresh
时,将导致在内部调用
/internal/cgi/apache/nph-refresh?f=/u/foo/bar/page.html
/internal/cgi/apache/nph-refresh?f=/u/foo/bar/page.html
接着就是NPH-CGI脚本部分了。虽然,人们常说"将此作为一个练习留给读者",但我还是给出答案了。
#!/sw/bin/perl
##
## nph-refresh -- 用于自动刷新页面的 NPH/CGI 脚本
## Copyright (c) 1997 Ralf S. Engelschall, All Rights Reserved.
##
$| = 1;
#!/sw/bin/perl
##
## nph-refresh -- 用于自动刷新页面的 NPH/CGI 脚本
## Copyright (c) 1997 Ralf S. Engelschall, All Rights Reserved.
##
$| = 1;
# 分解 QUERY_STRING 变量
@pairs = split(/&/, $ENV{'QUERY_STRING'});
foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);
$name =~ tr/A-Z/a-z/;
$name = 'QS_' . $name;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
eval "\$$name = \"$value\"";
}
$QS_s = 1 if ($QS_s eq ");
$QS_n = 3600 if ($QS_n eq ");
if ($QS_f eq ") {
print "HTTP/1.0 200 OK\n";
print "Content-type: text/html\n\n";
print "<b>ERROR</b>: No file given\n";
exit(0);
}
if (! -f $QS_f) {
print "HTTP/1.0 200 OK\n";
print "Content-type: text/html\n\n";
print "<b>ERROR</b>: File $QS_f not found\n";
exit(0);
}
@pairs = split(/&/, $ENV{'QUERY_STRING'});
foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);
$name =~ tr/A-Z/a-z/;
$name = 'QS_' . $name;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
eval "\$$name = \"$value\"";
}
$QS_s = 1 if ($QS_s eq ");
$QS_n = 3600 if ($QS_n eq ");
if ($QS_f eq ") {
print "HTTP/1.0 200 OK\n";
print "Content-type: text/html\n\n";
print "<b>ERROR</b>: No file given\n";
exit(0);
}
if (! -f $QS_f) {
print "HTTP/1.0 200 OK\n";
print "Content-type: text/html\n\n";
print "<b>ERROR</b>: File $QS_f not found\n";
exit(0);
}
sub print_http_headers_multipart_begin {
print "HTTP/1.0 200 OK\n";
$bound = "ThisRandomString12345";
print "Content-type: multipart/x-mixed-replace;boundary=$bound\n";
&print_http_headers_multipart_next;
}
print "HTTP/1.0 200 OK\n";
$bound = "ThisRandomString12345";
print "Content-type: multipart/x-mixed-replace;boundary=$bound\n";
&print_http_headers_multipart_next;
}
sub print_http_headers_multipart_next {
print "\n--$bound\n";
}
print "\n--$bound\n";
}
sub print_http_headers_multipart_end {
print "\n--$bound--\n";
}
print "\n--$bound--\n";
}
sub displayhtml {
local($buffer) = @_;
$len = length($buffer);
print "Content-type: text/html\n";
print "Content-length: $len\n\n";
print $buffer;
}
local($buffer) = @_;
$len = length($buffer);
print "Content-type: text/html\n";
print "Content-length: $len\n\n";
print $buffer;
}
sub readfile {
local($file) = @_;
local(*FP, $size, $buffer, $bytes);
($x, $x, $x, $x, $x, $x, $x, $size) = stat($file);
$size = sprintf("%d", $size);
open(FP, "<$file");
$bytes = sysread(FP, $buffer, $size);
close(FP);
return $buffer;
}
local($file) = @_;
local(*FP, $size, $buffer, $bytes);
($x, $x, $x, $x, $x, $x, $x, $size) = stat($file);
$size = sprintf("%d", $size);
open(FP, "<$file");
$bytes = sysread(FP, $buffer, $size);
close(FP);
return $buffer;
}
$buffer = &readfile($QS_f);
&print_http_headers_multipart_begin;
&displayhtml($buffer);
&print_http_headers_multipart_begin;
&displayhtml($buffer);
sub mystat {
local($file) = $_[0];
local($time);
local($file) = $_[0];
local($time);
($x, $x, $x, $x, $x, $x, $x, $x, $x, $mtime) = stat($file);
return $mtime;
}
return $mtime;
}
$mtimeL = &mystat($QS_f);
$mtime = $mtime;
for ($n = 0; $n < $QS_n; $n++) {
while (1) {
$mtime = &mystat($QS_f);
if ($mtime ne $mtimeL) {
$mtimeL = $mtime;
sleep(2);
$buffer = &readfile($QS_f);
&print_http_headers_multipart_next;
&displayhtml($buffer);
sleep(5);
$mtimeL = &mystat($QS_f);
last;
}
sleep($QS_s);
}
}
$mtime = $mtime;
for ($n = 0; $n < $QS_n; $n++) {
while (1) {
$mtime = &mystat($QS_f);
if ($mtime ne $mtimeL) {
$mtimeL = $mtime;
sleep(2);
$buffer = &readfile($QS_f);
&print_http_headers_multipart_next;
&displayhtml($buffer);
sleep(5);
$mtimeL = &mystat($QS_f);
last;
}
sleep($QS_s);
}
}
&print_http_headers_multipart_end;
exit(0);
##EOF##
海量虚拟主机
描述:
Apache的<VirtualHost>功能很强,在有几十个虚拟主机的情况下运行得仍然很好,但是如果你是ISP,需要提供成百上千个虚拟主机,那么这就不是最佳选择了。
解决方案:
为此,需要用代理吞吐(Proxy Throughput)功能([P]标志)映射远程页面甚至整个远程网络区域到自己的域名空间:
##
## vhost.map
##
www.vhost1.dom:80 /path/to/docroot/vhost1
www.vhost2.dom:80 /path/to/docroot/vhost2
:
www.vhostN.dom:80 /path/to/docroot/vhostN
描述:
Apache的<VirtualHost>功能很强,在有几十个虚拟主机的情况下运行得仍然很好,但是如果你是ISP,需要提供成百上千个虚拟主机,那么这就不是最佳选择了。
解决方案:
为此,需要用代理吞吐(Proxy Throughput)功能([P]标志)映射远程页面甚至整个远程网络区域到自己的域名空间:
##
## vhost.map
##
www.vhost1.dom:80 /path/to/docroot/vhost1
www.vhost2.dom:80 /path/to/docroot/vhost2
:
www.vhostN.dom:80 /path/to/docroot/vhostN
##
## httpd.conf
##
:
# 在重定向时使用规范化的主机名等等
UseCanonicalName on
## httpd.conf
##
:
# 在重定向时使用规范化的主机名等等
UseCanonicalName on
:
# 在CLF-format之前添加虚拟主机
CustomLog /path/to/access_log "%{VHOST}e %h %l %u %t \"%r\" %>s %b"
:
# 在CLF-format之前添加虚拟主机
CustomLog /path/to/access_log "%{VHOST}e %h %l %u %t \"%r\" %>s %b"
:
# 为主服务器启用重写引擎
RewriteEngine on
RewriteEngine on
# define two maps: one for fixing the URL and one which defines
# the available virtual hosts with their corresponding
# DocumentRoot.
RewriteMap lowercase int:tolower
RewriteMap vhost txt:/path/to/vhost.map
# the available virtual hosts with their corresponding
# DocumentRoot.
RewriteMap lowercase int:tolower
RewriteMap vhost txt:/path/to/vhost.map
# Now do the actual virtual host mapping
# via a huge and complicated single rule:
#
# 1. make sure we don't map for common locations
RewriteCond %{REQUEST_URI} !^/commonurl1/.*
RewriteCond %{REQUEST_URI} !^/commonurl2/.*
:
RewriteCond %{REQUEST_URI} !^/commonurlN/.*
#
# 2. make sure we have a Host header, because
# currently our approach only supports
# virtual hosting through this header
RewriteCond %{HTTP_HOST} !^$
#
# 3. lowercase the hostname
RewriteCond ${lowercase:%{HTTP_HOST}|NONE} ^(.+)$
#
# 4. lookup this hostname in vhost.map and
# remember it only when it is a path
# (and not "NONE" from above)
RewriteCond ${vhost:%1} ^(/.*)$
#
# 5. finally we can map the URL to its docroot location
# and remember the virtual host for logging puposes
RewriteRule ^/(.*)$ %1/$1 [E=VHOST:${lowercase:%{HTTP_HOST}}]
:
# via a huge and complicated single rule:
#
# 1. make sure we don't map for common locations
RewriteCond %{REQUEST_URI} !^/commonurl1/.*
RewriteCond %{REQUEST_URI} !^/commonurl2/.*
:
RewriteCond %{REQUEST_URI} !^/commonurlN/.*
#
# 2. make sure we have a Host header, because
# currently our approach only supports
# virtual hosting through this header
RewriteCond %{HTTP_HOST} !^$
#
# 3. lowercase the hostname
RewriteCond ${lowercase:%{HTTP_HOST}|NONE} ^(.+)$
#
# 4. lookup this hostname in vhost.map and
# remember it only when it is a path
# (and not "NONE" from above)
RewriteCond ${vhost:%1} ^(/.*)$
#
# 5. finally we can map the URL to its docroot location
# and remember the virtual host for logging puposes
RewriteRule ^/(.*)$ %1/$1 [E=VHOST:${lowercase:%{HTTP_HOST}}]
:
访问控制
对主机的拒绝
描述:
如何禁止一批外部列表中的主机对我们服务器的访问?
解决方案:
RewriteEngine on
RewriteMap hosts-deny txt:/path/to/hosts.deny
RewriteCond ${hosts-deny:%{REMOTE_HOST}|NOT-FOUND} !=NOT-FOUND [OR]
RewriteCond ${hosts-deny:%{REMOTE_ADDR}|NOT-FOUND} !=NOT-FOUND
RewriteRule ^/.* - [F]
##
## hosts.deny
##
## 注意! 这是一个映射而不是列表(即使我们这样看待它)。
## mod_rewrite会将它作为 键/值 对进行解析。
## 所以每一项至少要存在一个伪值:"-"
##
## hosts.deny
##
## 注意! 这是一个映射而不是列表(即使我们这样看待它)。
## mod_rewrite会将它作为 键/值 对进行解析。
## 所以每一项至少要存在一个伪值:"-"
##
193.102.180.41 -
bsdti1.sdm.de -
192.76.162.40 -
bsdti1.sdm.de -
192.76.162.40 -
对代理的拒绝
描述:
如何拒绝某个主机或者来自特定主机的用户使用Apache代理?
解决方案:
首先,要确保Apache配置文件中mod_rewrite在mod_proxy的下面!使它在mod_proxy之前被调用。然后使用如下方法拒绝某个主机:
RewriteCond %{REMOTE_HOST} ^badhost\.mydomain\.com$
RewriteRule !^http://[^/.]\.mydomain.com.* - [F]
描述:
如何拒绝某个主机或者来自特定主机的用户使用Apache代理?
解决方案:
首先,要确保Apache配置文件中mod_rewrite在mod_proxy的下面!使它在mod_proxy之前被调用。然后使用如下方法拒绝某个主机:
RewriteCond %{REMOTE_HOST} ^badhost\.mydomain\.com$
RewriteRule !^http://[^/.]\.mydomain.com.* - [F]
使用如下方法拒绝user@host-dependent用户:
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} ^badguy@badhost\.mydomain\.com$
RewriteRule !^http://[^/.]\.mydomain.com.* - [F]
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} ^badguy@badhost\.mydomain\.com$
RewriteRule !^http://[^/.]\.mydomain.com.* - [F]
特殊的认证
描述:
有时候,会需要一种非常特殊的认证,即对一组明确指定的用户,允许其访问,且没有(在使用mod_auth_basic的基本认证方法时可能会出现的)任何提示。
解决方案:
使用一个重写条件列表来拒绝朋友以外的所有人:
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^friend1@client1.quux-corp\.com$
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^friend2@client2.quux-corp\.com$
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^friend3@client3.quux-corp\.com$
RewriteRule ^/~quux/only-for-friends/ - [F]
描述:
有时候,会需要一种非常特殊的认证,即对一组明确指定的用户,允许其访问,且没有(在使用mod_auth_basic的基本认证方法时可能会出现的)任何提示。
解决方案:
使用一个重写条件列表来拒绝朋友以外的所有人:
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^friend1@client1.quux-corp\.com$
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^friend2@client2.quux-corp\.com$
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^friend3@client3.quux-corp\.com$
RewriteRule ^/~quux/only-for-friends/ - [F]
基于Referer的转向器
描述:
如何配置一个基于HTTP头"Referer"的转向器以转向到任意数量的引用页(referring page)?
解决方案:
使用下面这个很巧妙的规则集:
RewriteMap deflector txt:/path/to/deflector.map
描述:
如何配置一个基于HTTP头"Referer"的转向器以转向到任意数量的引用页(referring page)?
解决方案:
使用下面这个很巧妙的规则集:
RewriteMap deflector txt:/path/to/deflector.map
RewriteCond %{HTTP_REFERER} !=""
RewriteCond ${deflector:%{HTTP_REFERER}} ^-$
RewriteRule ^.* %{HTTP_REFERER} [R,L]
RewriteCond ${deflector:%{HTTP_REFERER}} ^-$
RewriteRule ^.* %{HTTP_REFERER} [R,L]
RewriteCond %{HTTP_REFERER} !=""
RewriteCond ${deflector:%{HTTP_REFERER}|NOT-FOUND} !=NOT-FOUND
RewriteRule ^.* ${deflector:%{HTTP_REFERER}} [R,L]
RewriteCond ${deflector:%{HTTP_REFERER}|NOT-FOUND} !=NOT-FOUND
RewriteRule ^.* ${deflector:%{HTTP_REFERER}} [R,L]
并结合相应的重写映射表:
##
## deflector.map
##
它可以自动将请求(在映射表中指定了"-"值的时候)重定向回其引用页(referring page),或者(在映射表中URL有第二个参数时)重定向到一个特定的URL。
##
## deflector.map
##