Apache的ReWrite的應用

日期:2008-07-02  作者:喜騰小二  來源:PHPChina


Apache的mod_rewrite是提供了強大URL操作的殺手級的模塊,可以實現幾乎所有妳夢想的URL操作類型,其代價是妳必須接受其復雜性,因為mod_rewrite的主要障礙就是初學者不容易理解和運用,即使是Apache專家有時也會發掘出mod_rewrite的新用途。
換句話說:對mod_rewrite,或者是打退堂鼓永不再用,或者是喜歡它並一生受用。
ReWrite可以應用在以下方麵或者解決以下問題:
URL的規劃
規範的URL
說明:
在有些網站服務器上,一個資源會擁有多個URL,在實際應用和發佈中應該被使用的是規範的URL,其他的則是簡寫或者是內部使用的。無論用戶在請求中使用什麼形式的URL,他最終看見的都應該是規範的URL。

方案:
對所有的不規範的URL執行一個外部的HTTP重定嚮,以改變它在浏覽器地址欄中的顯示及其後繼的請求。下例中的規則集用規範的/u/user替換/~user,並修正了/u/user所遺漏的後綴的斜槓。

代碼:
RewriteRule ^/~([^/]+)/?(.*) /u/$1/$2 [R]
RewriteRule ^/([uge])/([^/]+)$ /$1/$2/ [R]

規範的主機名
說明:
...
方案:
代碼:
RewriteCond % !^fully.qualified.domain.name [NC]
RewriteCond % !^$
RewriteCond % !^80$
RewriteRule ^/(.*) http://fully.qualified.domain.name:%/$1 [L,R]
RewriteCond % !^fully.qualified.domain.name [NC]
RewriteCond % !^$
RewriteRule ^/(.*) http://fully.qualified.domain.name/$1 [L,R]

被移動過的DocumentRoot
說明:
通常,網站服務器的DocumentRoot直接對應於URL"/",但是,它常常不是處於最高一級,而可能隻是眾多數據池中的一個實體。比如,在 Intranet站點中,有/e/www/(WWW的主頁)、/e/sww/ (Intranet的主頁)等等,而DocumentRoot指嚮了/e/www/,則必須保證此數據池中的所有內嵌的圖片和其他元素對後繼請求有效。

方案:
隻須重定嚮URL /到/e/www/即可。這個方案看起來很簡單,但隻是有了mod_rewrite模塊的支持,它才簡單,因為傳統的URL Aliases機制(由mod_alias及其相關模塊提供)隻是作了一個前綴匹配,DocumentRoot是一個對所有URL的前綴,因而無法實現這樣的重定嚮。而用mod_rewrite的確很簡單:

代碼:
RewriteEngine on
RewriteRule ^/$ /e/www/ [R]

後綴斜槓的問題
說明:
每個網管對引用目錄後綴斜槓的問題都有一本苦經,如果遺漏了,服務器會產生一個錯誤,因為如果請求是/~quux/foo而不是/~quux/foo/,服務器會去找一個叫foo的文件,而它是一個目錄,所以就報錯了。事實上,大多數情況下,它自己會試圖修正這個錯誤,但是有時候需要妳手工糾正,比如,在重寫了許多CGI腳本中的復雜的URL以後。

方案:
解決這個微妙問題的方案是讓服務器自動添加後綴的斜槓。對此,必須使用一個外部的重定嚮,使浏覽器正確地處理後繼的對諸如圖片的請求。如果僅僅作一個內部的重寫,可能隻對目錄页面有效,而對內嵌有使用相對URL的圖片的页面則無效,因為浏覽器有請求內嵌目標的可能。比如,如果不用外部重定嚮,/~quux/foo/index.html页面中對image.gif的請求,其結果將是/~quux/image.gif!。
所以,應該這樣寫:

代碼:
RewriteEngine on
RewriteBase /~quux/
RewriteRule ^foo$ foo/ [R]

又懶又瘋狂的做法是把這些寫入其宿主目錄中的頂級.htaccess中,但是須注意,如此會帶來一些處理上的開銷。
代碼:
RewriteEngine on
RewriteBase /~quux/
RewriteCond % -d
RewriteRule ^(.+[^/])$ $1/ [R]

集群網站的同類URL規劃
說明:
我們希望在一個Intranet集群網站中,對所有WWW服務器建立一個同類的一緻性的URL規劃,也就是,所有的URL(對單個服務器來說,是本地的依賴於此服務器的!)是獨立於服務器的!我們需要的是一個俱有獨立於服務器的一緻性規劃的WWW名稱空間,即,URL不需要包含正確的物理的目標服務器,而由集群本身來自動定位物理的目標主機。

方案:
首先,目標服務器的信息來自(產生)於包含有用戶、組以及實體的外部地圖,其格式形如:

代碼:
user1 server_of_user1
user2 server_of_user2
: :

這些信息被存入map.xxx-to-host文件。其次,如果URL在一個服務器上無效,需要引導所有的服務器重定嚮URL

代碼:
/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

以下規則集依靠地圖文件來完成這個操作(假定,如果一個用戶在地圖中沒有對應的項,則使用server0為默認服務器):

代碼:
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

RewriteRule ^/u/([^/]+)/?(.*) http://$/u/$1/$2
RewriteRule ^/g/([^/]+)/?(.*) http://$/g/$1/$2
RewriteRule ^/e/([^/]+)/?(.*) http://$/e/$1/$2

RewriteRule ^/([uge])/([^/]+)/?$ /$1/$2/.www/
RewriteRule ^/([uge])/([^/]+)/([^.]+.+) /$1/$2/.www/$3

移動宿主目錄到不同的網站服務器
說明:
通常,許多網管在建立一個新的網站服務器時,都會有這樣的要求:重定嚮一個網站服務器上的所有宿主目錄到另一個網站服務器。

方案:
很簡單,用mod_rewrite。在老的網站服務器上重定嚮所有的URL /~user/anypath到http://newserver/~user/anypath。

代碼:
RewriteEngine on
RewriteRule ^/~(.+) http://newserver/~$1 [R,L]

結構化的宿主目錄
說明:
一些擁有幾韆個用戶的網站通常都使用結構化的宿主目錄規劃,即,每個宿主目錄位於一個帶有特定前綴比如其用戶名的第一個字符的子目錄下。那麼,/~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

文件繫統的重組
說明:
這是一個不加雕琢的例子:一個大量使用針對目錄的規則集以實現平滑觀感,而從來不用調整數據結構的殺手級的應用。背景:net.sw從1992年開始,存放了我收集的免費的有效的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

其中的DATA/子目錄包含了上述目錄結構,即實在的net.sw,由rdist在需要的時候自動更新。第二個部分的遺留問題是:如何連接這兩個結構為一個平滑觀感的URL樹?我希望在運行適當的CGI腳本而使用各種URL的時候,使用戶感覺不到DATA/目錄的存在。方案如下:首先,我把下列配置放在服務器上DocumentRoot中的針對目錄的配置文件裡,以重寫公佈的URL /net.sw/ 為內部路徑 /e/netsw:

代碼:
RewriteRule ^net.sw$ net.sw/ [R]
RewriteRule ^net.sw/(.*)$ e/netsw/$1

第一條規則是針對遺漏後綴斜槓的請求的!第二條規則才是真正實現功能的。接着,就是放在針對目錄的配置文件/e/netsw/.www/.wwwacl中的殺手級的配置了:

代碼:
Options ExecCGI FollowSymLinks Includes MultiViews

RewriteEngine on

# we are reached via /net.sw/ prefix
RewriteBase /net.sw/

# first we rewrite the root dir to
# the handling cgi script
RewriteRule ^$ netsw-home.cgi [L]
RewriteRule ^index.html$ netsw-home.cgi [L]

# strip out the subdirs when
# the browser requests us from perdir pages
RewriteRule ^.+/(netsw-[^/]+/.+)$ $1 [L]

# and now break the rewriting for local files
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]

# anything else is a subdir which gets handled
# by another cgi script
RewriteRule !^netsw-lsdir.cgi.* - [C]
RewriteRule (.*) netsw-lsdir.cgi/$1

閱讀提示:

注意前半部分中的標誌L(最後),和無對應項('-')
注意後半部分中的符號!(非),和標誌C (鏈)
注意最後一條規則的全匹配模式
代碼:
NCSA imagemap和Apache mod_imap
說明:
許多人都希望在從NCSA網站服務器嚮較現代的Apache網站服務器轉移中實現平滑過渡,即希望老的NCSA imagemap程序能在Apache的較現代的mod_imap支持下正常運作。但問題在於,到處都是通過/cgi-bin/imagemap /path/to/page.map引用imagemap程序的連接,而在Apache下,應該寫成/path/to/page.map。

方案:
使用全局規則在空閒時間去除所有這些請求的前綴:

代碼:
RewriteEngine on
RewriteRule ^/cgi-bin/imagemap(.*) $1 [PT]

在多個目錄中搜索页面
說明:
有時會有必要使網站服務器在多個目錄中搜索页面,對此,MultiViews或者其他技術無能為力。

方案:
編制一個明確的規則集以搜索目錄中的文件。

代碼:
RewriteEngine on

# first try to find it in custom/...
# ...and if found stop and be happy:
RewriteCond /your/docroot/dir1/% -f
RewriteRule ^(.+) /your/docroot/dir1/$1 [L]

# second try to find it in pub/...
# ...and if found stop and be happy:
RewriteCond /your/docroot/dir2/% -f
RewriteRule ^(.+) /your/docroot/dir2/$1 [L]

# else go on for other Alias or ScriptAlias directives,
# etc.
RewriteRule ^(.+) - [PT]

按照URL的片段設置環境變量
說明:
如果希望保持請求之間的狀態信息,但又不希望使用CGI來包裝所有页面,而隻通過分離URL中的有用信息來編碼。

方案:
可以用一個規則集來分離出狀態信息,並設置環境變量以備此後用於XSSI或CGI。如此,一個/foo/S=java/bar/的URL會被解析為/foo/bar/,而環境變量STATUS則被設置為"java"。

代碼:
RewriteEngine on
RewriteRule ^(.*)/S=([^/]+)/(.*) $1/$3 [E=STATUS:$2]

虛擬用戶主機
說明:
如果需要為用戶username支持一個www.username.host.domain.com的主頁?..鍬嫉?u>方法實現?/a>

方案:
對HTTP/1.0的請求,這是無法實現的;但是對HTTP/1.1的在HTTP頭中包含有主機名的請求,可以用以下規則集來內部地重寫http://www.username.host.com/anypat...rname/anypath:

代碼:
RewriteEngine on
RewriteCond % ^www.[^.]+.host.com$
RewriteRule ^(.+) %$1 [C]
RewriteRule ^www.([^.]+).host.com(.*) /home/$1$2

為外來訪問者重定嚮宿主目錄
說明:
對不是來自本地域ourdomain.com的外來訪問者的請求,重定嚮其宿主目錄URL到另一個網站服務器www.somewhere.com,有時這種做法也會...骰納舷攣鬧小?/a>

方案:
隻須一個重寫條件:

代碼:
RewriteEngine on
RewriteCond % !^.+.ourdomain.com$
RewriteRule ^(/~.+) http://www.somewhere.com/$1 [R,L]

重定嚮失敗的URL到其他網站服務器
說明:
如何重寫URL以重定嚮對網站服務器A的失敗請求到服務器B,是一個常見的問題。一般,可以用Perl寫的CGI腳本通過ErrorDocument來解決,此外,還有mod_rewrite方案。但是須注意,這種方法的執行效率不如用ErrorDocument的CGI腳本!

方案:
第一種方案,有最好的性能而靈活性欠佳,出錯概率小所以安全:

代碼:
RewriteEngine on
RewriteCond /your/docroot/% !-f
RewriteRule ^(.+) http://webserverB.dom/$1

但是其問題在於,它隻對位於DocumentRoot中的页面有效。雖然可以增加更多的條件(比如同時還處理宿主目錄,等等),但是還有一個更好的方法:

代碼:
RewriteEngine on
RewriteCond % !-U
RewriteRule ^(.+) http://webserverB.dom/$1

這種方法使用了mod_rewrite提供的“嚮前參照(look-ahead)”的功能,是一種對所有URL類型都有效而且安全的方法。但是,對網站服務器的性能會有影響,所以如果網站服務器有一個強大的CPU,那就用這個方法。而在慢速機器上,可以用第一種方法,或者用性能更好的 ErrorDocument CGI腳本。

擴展的重定嚮
說明:
有時候,我們會需要更多的對重定嚮URL的(有關字符轉義機制方麵的)控制。通常,Apache內核中的URL轉義函數uri_escape()同時還會對anchor轉義,即,類似"url#anchor"的URL,因此,妳不能用mod_rewrite對此類URL直接重定嚮。那麼如何實現呢?

方案:
必須用NPH-CGI腳本使它自己重定嚮,因為對NPH(non-parseable headers [無須解析的HTTP頭])不會發生轉義操作。首先,在針對服務器的配置中(應該位於所有重寫規則的最後),引入一種新的URL類型xredirect::

代碼:
RewriteRule ^xredirect.+) /path/to/nph-xredirect.cgi/$1
[T=application/x-httpd-cgi,L]

以強制所有帶xredirect:前綴的URL被傳送到如下的nph-xredirect.cgi程序:

代碼:
#!/path/to/perl
##
## nph-xredirect.cgi -- NPH/CGI script for extended redirects
## Copyright (c) 1997 Ralf S. Engelschall, All Rights Reserved.
##

$| = 1;
$url = $ENV;

print "HTTP/1.0 302 Moved Temporarily
";
print "Server: $ENV
";
print "Location: $url
";
print "Content-type: text/html
";
print "
";
print "<html>
";
print "<head>
";
print "<title>302 Moved Temporarily (EXTENDED)</title>
";
print "</head>
";
print "<body>
";
print "<h1>Moved Temporarily (EXTENDED)</h1>
";
print "The document has moved <a HREF="$url">here</a>.<p>
";
print "</body>
";
print "</html>
";

##EOF##

這是一種可以重定嚮所有URL類型的方法,包括不被mod_rewrite直接支持的類型。所以,還可以這樣重定嚮news:newsgroup:

代碼:
RewriteRule ^anyurl xredirect:news:newsgroup

注意:無須對上述規則加[R]或[R,L],因為xredirect:會在稍後被其特殊的傳送規則擴展。
文檔訪問的多路復用
說明:
妳知道http://www.perl.com/CPAN的CPAN(Comprehensive Perl Archive Network)嗎?它實現了一個重定嚮以提供,全世界的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/(.*) %::$1 [C]
RewriteRule ^.+.([a-zA-Z]+):.*)$ $$2 [R,L]

##
## map.cxan -- Multiplexing Map for CxAN
##

de ftp://ftp.cxan.de/CxAN/
uk ftp://ftp.cxan.uk/CxAN/
com ftp://ftp.cxan.com/CxAN/
:
##EOF##

依賴於時間的重寫
說明:
在页面內容依時間不同而變化的場合,比如重定嚮特定页面,許多網管仍然采用CGI腳本的方法,如何用mod_rewrite來實現呢?

方案:
有許多類似TIME_xxx的變量可以用在重寫條件中,利用<STRING,>STRING和=STRING的類型比較,並加以連接,就可以實現依賴於時間的重寫:

代碼:
RewriteEngine on
RewriteCond %% >0700
RewriteCond %% <1900
RewriteRule ^foo.html$ foo.day.html
RewriteRule ^foo.html$ foo.night.html

此例使URL foo.html在07:00-19:00時指嚮foo.day.html,而在其餘時間,則指嚮foo.night.html,對主頁是一個不錯的功能...

對YYYY過渡為XXXX的嚮前兼容
說明:
在轉變了大批.html文件為.phtml,使文檔.YYYY過渡成為文檔.XXXX後,如何保持URL的嚮前兼容(仍然虛擬地存在)?

方案:
隻須按基準文件名重寫,並測試帶有新的擴展名的文件是否存在,如果存在,則用新的,否則,仍然用原來的。

代碼:
# backward compatibility ruleset for
# rewriting document.html to document.phtml
# when and only when document.phtml exists
# but no longer document.html
RewriteEngine on
RewriteBase /~quux/
# parse out basename, but remember the fact
RewriteRule ^(.*).html$ $1 [C,E=WasHTMLs]
# rewrite to document.phtml if exists
RewriteCond %.phtml -f
RewriteRule ^(.*)$ $1.phtml [S=1]
# else reverse the previous basename cutout
RewriteCond % ^yes$
RewriteRule ^(.*)$ $1.html

內容的處理
新舊URL(內部的)
說明:
假定已經把文件bar.html改名為foo.html,需要對老的URL嚮前兼容,即讓用戶仍然可以使用老的URL,而感覺不到文件被改名了。

方案:
通過以下規則內部地重寫老的URL為新的:

代碼:
RewriteEngine on
RewriteBase /~quux/
RewriteRule ^foo.html$ bar.html

新舊URL(外部的)
說明:
仍然假定已經把文件bar.html改名為foo.html,需要對老的URL嚮前兼容,但是要讓用戶得到文件被改名的暗示,即,其浏覽器的地址欄中顯示的是新的URL。

方案:
作一個HTTP的強制重定嚮以改變浏覽器和用戶界麵上的顯示:

代碼:
RewriteEngine on
RewriteBase /~quux/
RewriteRule ^foo.html$ bar.html [R]

依賴於浏覽器的內容
說明:
至少對重要的頂級页面,有時候有必要提供依賴於浏覽器的最佳的內容,即對最新的Netscape提供最大化的版本,對Lynx提供最小化的版本,而對其他的浏覽器則提供一個功能一般的版本。

方案:
對此,內容協商無能為力,因為浏覽器不提供其那種形式的類型,所以隻能在HTTP頭"User-Agent"上想辦法。以下規則集可以完成這個操作:如果 HTTP頭"User-Agent"以"Mozilla/3"開頭,則页面foo.html被重寫為foo.NS.html,而後重寫操作終止;如果是"Lynx"或者版本號為1和2的"Mozilla",則重寫為foo.20.html;而其他所有的浏覽器收到的页面則是foo.32.html:

代碼:
RewriteCond % ^Mozilla/3.*
RewriteRule ^foo.html$ foo.NS.html [L]
RewriteCond % ^Lynx/.* [OR]
RewriteCond % ^Mozilla/[12].*
RewriteRule ^foo.html$ foo.20.html [L]
RewriteRule ^foo.html$ foo.32.html [L]

動態鏡像
說明:
假定,需要在我們的名稱空間裡加入其他遠程主機的页面。對FTP服務器,可以用mirror程序以在本地機器上維持一個對遠程數據的最新的拷貝;對網站服務器,可以用類似的用於HTTP的webcopy程序。但這兩種技術都有一個主要的缺點:此本地拷貝必須通過這個程序的執行來更新。所以,比較好的方法是,不采用靜態鏡像,而采用動態鏡像,即,在有數據請求時自動更新(遠程主機上更新的數據)。

方案:
為此,使用Proxy Throughput功能(flag [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]

反嚮動態鏡像
說明:
...
方案:
代碼:
RewriteEngine on
RewriteCond /mirror/of/remotesite/$1 -U
RewriteRule ^http://www.remotesite.com/(.*)$ /mirror/of/remotesite/$1

通過Intranet取得丟失的數據
說明:
這是一種在受防火牆保護的(內部的)Intranet(www2.quux-corp.dom)上保存和維護實際數據,而虛擬地運行企業級(外部的)Internet網站服務器(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

按妳的實際配置,只要對上例稍作調整即可。接着,建立通過代理後臺獲取丟失數據的mod_rewrite規則:

代碼:
RewriteRule ^/~([^/]+)/?(.*) /home/$1/.www/$2
RewriteCond % !-f
RewriteCond % !-d
RewriteRule ^/home/([^/]+)/.www/?(.*) http://www2.quux-corp.dom/~$1/pub/$2 [P]

負載的均衡
說明:
如何均衡www.foo.com的負載到www[0-5].foo.com(一共是6個服務器)?

方案:
這個問題有許多可能的解決方案,在此,我們討論通稱為“基於DNS(DNS-based)的”方案,和特殊的使用mod_rewrite的方案:

DNS循環(DNS Round-Robin)
最簡單的方法是用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

然後,增加以下各項:

代碼:
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,則...⒌礁鞲龇衿髁?/a>

DNS 負載均衡
一種成熟的基於DNS的負載均衡方法是使用http://www.stanford.edu/~schemers/d...颍且桓鯬erl 5程序,帶有若乾輔助工俱,實現了真正的基於DNS的負載均衡。

代理吞吐循環(Proxy Throughput Round-Robin)
這是一個使用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 ^/(.+)$ $ [P,L]

以下是lb.pl:

代碼:
#!/path/to/perl
##
## lb.pl -- load balancing script
##

$| = 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
$cnt = 0;
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層的負載均衡,事實上,它是一個位於網站集群前端的電路級網關。如果妳有足夠資金而且的確需要高性能的解決方案,那麼可以用這個。

反嚮代理
說明:
...
方案:
代碼:
##
## apache-rproxy.conf -- Apache configuration for Reverse Proxy Usage
##
# server type
ServerType standalone
Listen 8000
MinSpareServers 16
StartServers 16
MaxSpareServers 16
MaxClients 16
MaxRequestsPerChild 100
# server operation parameters
KeepAlive on
MaxKeepAliveRequests 100
KeepAliveTimeout 15
Timeout 400
IdentityCheck off
HostnameLookups off

# paths to runtime files
PidFile /path/to/apache-rproxy.pid
LockFile /path/to/apache-rproxy.lock
ErrorLog /path/to/apache-rproxy.elog
CustomLog /path/to/apache-rproxy.dlog "%t %h -> %e URL: %U"

# unused paths
ServerRoot /tmp
DocumentRoot /tmp
CacheRoot /tmp
RewriteLog /dev/null
TransferLog /dev/null
TypesConfig /dev/null
AccessConfig /dev/null
ResourceConfig /dev/null

# speed up and secure processing
<Directory />
Options -FollowSymLinks -SymLinksIfOwnerMatch
AllowOverride None
</Directory>

# the status page for monitoring the reverse proxy
<Location /apache-rproxy-status>
SetHandler server-status
</Location>

# enable the URL rewriting engine
RewriteEngine on
RewriteLogLevel 0

# define a rewriting map with value-lists where
# mod_rewrite randomly chooses a particular value
RewriteMap server rnd:/path/to/apache-rproxy.conf-servers

# make sure the status page is handled locally
# and make sure no one uses our proxy except ourself
RewriteRule ^/apache-rproxy-status.* - [L]
RewriteRule ^(http|ftp)://.* - [F]

# now choose the possible servers for particular URL types
RewriteRule ^/(.*.(cgi|shtml))$ to://$/$1 [S=1]
RewriteRule ^/(.*)$ to://$/$1

# and delegate the generated URL by passing it
# through the proxy module
RewriteRule ^to://([^/]+)/(.*) http://$1/$2 [E=SERVER:$1,P,L]

# and make really sure all other stuff is forbidden
# when it should survive the above rules...
RewriteRule .* - [F]

# enable the Proxy module without caching
ProxyRequests on
NoCache *

# setup URL reverse mapping for redirect reponses
ProxyPassReverse / http://www1.foo.dom/
ProxyPassReverse / http://www2.foo.dom/
ProxyPassReverse / http://www3.foo.dom/
ProxyPassReverse / http://www4.foo.dom/
ProxyPassReverse / http://www5.foo.dom/
ProxyPassReverse / http://www6.foo.dom/

##
## apache-rproxy.conf-servers -- Apache/mod_rewrite selection table
##

# list of backend servers which serve static
# pages (HTML files and Images, etc.)
static www1.foo.dom|www2.foo.dom|www3.foo.dom|www4.foo.dom

# list of backend servers which serve dynamically
# generated page (CGI programs or mod_perl scripts)
dynamic www5.foo.dom|www6.foo.dom

新的MIME類型,新的服務
說明:
網上有許多很技巧的CGI程序,但是用法晦澀,許多網管鍥之不用。即使是Apache的MEME類型的動作處理器,也僅僅在CGI程序不需要在其輸入中包含特殊URL(PATH_INFO和QUERY_STRINGS)時才很好用。首先,配置一種新的後綴為.scgi(for secure 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/

其缺點是,必須同時硬編碼超鏈中的區域和CGI的路徑,如果重組了這個區域,就需要花費大量時間來修改各個超鏈。

方案:
方案是用一個特殊的新的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="*"

它會被內部地自動轉換為

代碼:
/internal/cgi/user/wwwidx?i=/u/user/foo/

如此,可以為使用:log的超鏈,拼裝出調用CGI程序的參數。

從靜態到動態
說明:
如何無縫轉換靜態页面foo.html為動態的foo.cgi,而不為浏覽器/用戶所察覺。

方案:
隻須重寫此URL為CGI-script,以強制為可以作為CGI-script運行的正確的MIME類型。如此,對/~quux/foo.html的請求其實會執行/~quux/foo.cgi。

代碼:
RewriteEngine on
RewriteBase /~quux/
RewriteRule ^foo.html$ foo.cgi [T=application/x-httpd-cgi]

空閒時間內的內容協商
說明:
這是一個很難解的功能:動態生成的靜態页面,即,它應該作為靜態页面發送(從文件繫統中讀出,然後直接發出去),但是如果它丟失了,則由服務器動態生成。如此,可以靜態地提供CGI生成的页面,除非有人(或者是一個cronjob)刪除了這些靜態页面,而且其內容可以得到更新。

方案:
以下規則集實現這個功能:
代碼:
RewriteCond % !-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即可(通常由一個cronjob完成)。

自動更新的文檔
說明:
建立一個復雜的页面,能夠在用編輯器寫了一個更新的版本時自動在浏覽器上得到刷新,這不是很好嗎?這可能嗎?

方案:
這是可行的! 這需要綜合利用MIME多成分、網站服務器的NPH和mod_rewrite的URL操控特性。首先,建立一個新的URL特性:對在文件繫統中更新時需要刷新的所有URL加上:refresh。

代碼:
RewriteRule ^(/[uge]/[^/]+/?.*):refresh /internal/cgi/apache/nph-refresh?f=$1

然後,修改URL

代碼:
/u/foo/bar/page.html:refresh

以內部地操控此URL

代碼:
/internal/cgi/apache/nph-refresh?f=/u/foo/bar/page.html

接着就是NPH-CGI腳本部分了。雖然,人們常說"left as an exercise to the reader",我還是給出答案了。

代碼:
#!/sw/bin/perl
##
## nph-refresh -- NPH/CGI script for auto refreshing pages
## Copyright (c) 1997 Ralf S. Engelschall, All Rights Reserved.
##
$| = 1;

# split the QUERY_STRING variable
@pairs = split(/&/, $ENV);
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
";
print "Content-type: text/html

";
print "&b&ERROR&/b&: No file given
";
exit(0);
}
if (! -f $QS_f) {
print "HTTP/1.0 200 OK
";
print "Content-type: text/html

";
print "&b&ERROR&/b&: File $QS_f not found
";
exit(0);
}

sub print_http_headers_multipart_begin {
print "HTTP/1.0 200 OK
";
$bound = "ThisRandomString12345";
print "Content-type: multipart/x-mixed-replace;boundary=$bound
";
&print_http_headers_multipart_next;
}

sub print_http_headers_multipart_next {
print "
--$bound
";
}

sub print_http_headers_multipart_end {
print "
--$bound--
";
}

sub displayhtml {
local($buffer) = @_;
$len = length($buffer);
print "Content-type: text/html
";
print "Content-length: $len

";
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;
}

$buffer = &readfile($QS_f);
&print_http_headers_multipart_begin;
&displayhtml($buffer);

sub mystat {
local($file) = $_[0];
local($time);

($x, $x, $x, $x, $x, $x, $x, $x, $x, $mtime) = stat($file);
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);
}
}

&print_http_headers_multipart_end;

exit(0);

##EOF##
大型虛擬主機
說明:
Apache的<VirtualHost>功能很強,在有幾十個虛擬主機的情況下運行得很好,但是如果妳是ISP,需要提供幾百個虛擬主機,那麼這就不是一個最佳的選擇了。

方案:
為此,需要用代理吞吐(Proxy Throughput)功能(flag [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
##
:
# use the canonical hostname on redirects, etc.
UseCanonicalName on

:
# add the virtual host in front of the CLF-format
CustomLog /path/to/access_log "%e %h %l %u %t "%r" %>s %b"
:

# enable the rewriting engine in the main server
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

# 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 % !^/commonurl1/.*
RewriteCond % !^/commonurl2/.*
:
RewriteCond % !^/commonurlN/.*
#
# 2. make sure we have a Host header, because
# currently our approach only supports
# virtual hosting through this header
RewriteCond % !^$
#
# 3. lowercase the hostname
RewriteCond $|NONE} ^(.+)$
#
# 4. lookup this hostname in vhost.map and
# remember it only when it is a path
# (and not "NONE" from above)
RewriteCond $ ^(/.*)$
#
# 5. finally we can map the URL to its docroot location
# and remember the virtual host for logging puposes
RewriteRule ^/(.*)$ %1/$1 [E=VHOST:$}]
:

對訪問的限制
阻止Robots
說明:
如何阻止一個完全匿名的robot取得特定網絡區域的页面?一個/robots.txt文件可以包含若乾"Robot Exclusion Protocol(robot排除協議)"的行,但不足以阻止此類robot。

方案:
可以用一個規則集以拒絕對網絡區域/~quux/foo/arc/(對一個很深的目錄區域進行列表可能會使服務器產生很大的負載)的訪問。還必須確保僅阻止特定的robot,就是說,僅僅阻止robot訪問主機是不夠的,這樣會同時也阻止了用戶訪問該主機。為此,就需要對HTTP頭的User-Agent信息作匹配。

代碼:
RewriteCond % ^NameOfBadRobot.*
RewriteCond % ^123.45.67.[8-9]$
RewriteRule ^/~quux/foo/arc/.+ - [F]
阻止內嵌的圖片
說明:
假設,http://www.quux-corp.de/~quux/有一?..庹庵質慮榉⑸?/a>

方案:
雖然,我們不能100%地保護這些圖片不被寫入別人的页面,但至少可以對發出HTTP Referer頭的浏覽器加以限制。

代碼:
RewriteCond % !^$
RewriteCond % !^http://www.quux-corp.de/~quux/.*$ [NC]
RewriteRule .*.gif$ - [F]
RewriteCond % !^$
RewriteCond % !.*/foo-with-gif.html$
RewriteRule ^inlined-in-foo.gif$ - [F]

對主機的拒絕
說明:
如何拒絕一批外部列表中的主機對我們服務器的使用?

方案:
代碼:
For Apache >= 1.3b6:

RewriteEngine on
RewriteMap hosts-deny txt:/path/to/hosts.deny
RewriteCond $|NOT-FOUND} !=NOT-FOUND [OR]
RewriteCond $|NOT-FOUND} !=NOT-FOUND
RewriteRule ^/.* - [F]

For Apache <= 1.3b6:

RewriteEngine on
RewriteMap hosts-deny txt:/path/to/hosts.deny
RewriteRule ^/(.*)$ $|NOT-FOUND}/$1
RewriteRule !^NOT-FOUND/.* - [F]
RewriteRule ^NOT-FOUND/(.*)$ $|NOT-FOUND}/$1
RewriteRule !^NOT-FOUND/.* - [F]
RewriteRule ^NOT-FOUND/(.*)$ /$1

代碼:
##
## hosts.deny
##
## ATTENTION! This is a map, not a list, even when we treat it as such.
## mod_rewrite parses it for key/value pairs, so at least a
## dummy value "-" must be present for each entry.
##

193.102.180.41 -
bsdti1.sdm.de -
192.76.162.40 -

對代理的拒絕
說明:
如何拒絕某個主機或者來自特定主機的用戶使用Apache代理?

方案:
首先,要確保Apache網站服務器在編譯時配置文件中mod_rewrite在mod_proxy的下麵(!),使它在mod_proxy之前被調用。然後,如下拒絕某個主機...

代碼:
RewriteCond % ^badhost.mydomain.com$
RewriteRule !^http://[^/.].mydomain.com.* - [F]

...如下拒絕user@host-dependent:

代碼:
RewriteCond %@% ^badguy@badhost.mydomain.com$
RewriteRule !^http://[^/.].mydomain.com.* - [F]

特殊的認證
說明:
有時候,會需要一種非常特殊的認證,即,對一組明確指定的用戶,允許其訪問,而沒有(在使用mod_access的基本認證方法時可能會出現的)任何提示。

方案:
可是使用一個重寫條件列表來排除所有的朋友:

代碼:
RewriteCond %@% !^friend1@client1.quux-corp.com$
RewriteCond %@% !^friend2@client2.quux-corp.com$
RewriteCond %@% !^friend3@client3.quux-corp.com$
RewriteRule ^/~quux/only-for-friends/ - [F]

基於提交者(Referer)的反射器
說明:
如何配置一個基於HTTP頭"Referer"的反射器以反射到任意數量的提交页面?

方案:
使用這個很技巧的規則集...

代碼:
RewriteMap deflector txt:/path/to/deflector.map

RewriteCond % !=""
RewriteCond $} ^-$
RewriteRule ^.* % [R,L]
RewriteCond % !=""
RewriteCond $|NOT-FOUND} !=NOT-FOUND
RewriteRule ^.* $} [R,L]

... 並結合對應的重寫地圖:

代碼:
##
## deflector.map
##

http://www.badguys.com/bad/index.html -
http://www.badguys.com/bad/index2.html -
http://www.badguys.com/bad/index3.html http://somewhere.com/

它可以自動將請求(在地圖中指定了"-"值的時候)反射回其提交页面,或者(在地圖中URL有第二個參數時)反射到一個特定的URL。

其他
外部重寫引擎
說明:
一個常見的問題: 如何解決似乎無法用mod_rewrite解決的FOO/BAR/QUUX/之類的問題?

方案:
可以使用一個與RewriteMap功能相同的外部RewriteMap程序,一旦它在Apache啓動時被執行,則從STDIN接收被請求的URL,並將處理過(通常是重寫過的)的URL(以相同順序!)在STDOUT輸出。

代碼:
RewriteEngine on
RewriteMap quux-map prg:/path/to/map.quux.pl
RewriteRule ^/~quux/(.*)$ /~quux/$


代碼:
#!/path/to/perl

# disable buffered I/O which would lead
# to deadloops for the Apache server
$| = 1;

# read URLs one per line from stdin and
# generate substitution URL on stdout
while (<> ) {
s|^foo/|bar/|;
print $_;
}

這是一個作演示的例子,隻是把所有的URL /~quux/foo/...重寫為/~quux/bar/...,而事實上,可以把它修改以獲得任何妳需要的功能。但是要注意,雖然一般用戶都可以使用,可是隻有繫統管理員才可以定義這樣的地圖。

<<<返回技術中心

技術文章

站內新聞

我要啦免费统计