# URL重寫指南
Originally written by
<cite class="calibre27">Ralf S. Engelschall <rse@apache.org></cite>
December 1997
本文是`mod_rewrite`的[參考文檔](#calibre_link-34),闡述在實際應用中如何解決網管所面臨的基于URL的典型問題,并詳細描述了如何配置URL重寫規則集以解決問題。
## `mod_rewrite`簡介
Apache的`mod_rewrite`是提供了強大URL操作的殺手級的模塊,可以實現幾乎所有你夢想的URL操作類型,其代價是你必須接受其復雜性,因為`mod_rewrite`的主要障礙就是初學者不容易理解和運用,即使是Apache專家有時也會發掘出`mod_rewrite`的新用途。換句話說:對于`mod_rewrite`,或者是打退堂鼓永不再用,或者是喜歡它并一生受用。本文試圖通過對已有方案的表述來創造一個成功的開端,以免你放棄。
## 實踐方案
我自己創造和收集了許多實踐方案,不要有畏懼心理,從這些例子開始學習URL重寫的黑匣子吧。
注意:根據你的服務器配置,可能有必要對例子作些微修改,比如,新啟用`mod_alias`和`mod_userdir`時要增加[PT]標志,或者重寫.htaccess而不是單個服務器中的規則集。對一個特定的規則集應該先透徹理解然后再考慮應用,這樣才能避免出現問題。
## URL 的規劃
### 規范的URL
說明:
在有些web服務器上,一個資源會擁有多個URL,在實際應用和發布中應該被使用的是規范的URL,其他的則是簡寫或者只在內部使用。無論用戶在請求中使用什么形式的URL,他最終看見的都應該是規范的URL。
方案:
對所有不規范的URL執行一個外部HTTP重定向,以改變它在瀏覽器地址欄中的顯示及其后繼請求。下例中的規則集用規范的/u/user替換/~user,并修正了/u/user所遺漏的后綴斜杠。
```
RewriteRule ^/**~**([^/]+)/?(.*) /**u**/$1/$2 [**R**]
RewriteRule ^/([uge])/(**[^/]+**)$ /$1/$2**/** [**R**]
```
### 規范的主機名
說明:
這個規則的目的是強制使用特定的主機名以代替其他名字。比如,你想強制使用 www.example.com 代替 example.com ,就可以在以下方法的基礎上進行修改:
方案:
```
# 針對運行在非80端口的站點
RewriteCond %{HTTP_HOST} !^fully\.qualified\.domain\.name [NC]
RewriteCond %{HTTP_HOST} !^$
RewriteCond %{SERVER_PORT} !^80$
RewriteRule ^/(.*) http://fully.qualified.domain.name:%{SERVER_PORT}/$1 [L,R]
# 對一個運行在80端口的站點
RewriteCond %{HTTP_HOST} !^fully\.qualified\.domain\.name [NC]
RewriteCond %{HTTP_HOST} !^$
RewriteRule ^/(.*) http://fully.qualified.domain.name/$1 [L,R]
```
### 移動過的`DocumentRoot`
說明:
通常,web服務器的`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**]
```
注意, 也可以通過`RedirectMatch`指令達到這個目的:
```
RedirectMatch ^/$ http://example.com/e/www/
```
### 后綴斜杠的問題
說明:
每個網管對引用目錄后綴斜杠的問題都有一本苦經,如果遺漏了,服務器會產生一個錯誤,因為如果請求是/~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 %{REQUEST_FILENAME} **-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://**${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 ^/([uge])/([^/]+)/?$ /$1/$2/.www/
RewriteRule ^/([uge])/([^/]+)/([^.]+.+) /$1/$2/.www/$3\
```
### 移動用戶主目錄到不同的web服務器
說明:
通常,許多網管在建立一個新的web服務器時,都會有這樣的要求:重定向一個web服務器上的所有用戶主目錄到另一個web服務器。
方案:
很簡單,在老的web服務器上重定向所有的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
# 我們通過"/net.sw/"前綴到達
RewriteBase /net.sw/
# 首先重寫根目錄到cgi處理腳本
RewriteRule ^$ netsw-home.cgi [L]
RewriteRule ^index\.html$ netsw-home.cgi [L]
# 當瀏覽器請求perdir頁面時剝去子目錄
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]
# 任何別的東西都是一個由另一個cgi腳本處理的子目錄
RewriteRule !^netsw-lsdir\.cgi.* - [C]
RewriteRule (.*) netsw-lsdir.cgi/$1
```
閱讀提示:
1. 注意前半部分中的標志L(最后),和無對應項("-")
2. 注意后半部分中的符號!(非),和標志C(鏈)
3. 注意最后一條規則的全匹配模式
### NCSA圖像映射和`mod_imap`
說明:
許多人都希望在從NCSA web服務器向較現代的Apache web服務器轉移中實現平滑過渡,即希望老的NCSA圖像映射程序能在Apache的較現代的`mod_imagemap`支持下正常運作。但問題在于,到處都是通過/cgi-bin/imagemap/path/to/page.map引用imagemap程序的連接,而在Apache下,應該寫成/path/to/page.map
方案:
使用全局規則在傳輸過程中去除所有這些請求的前綴:
```
RewriteEngine on
RewriteRule ^/cgi-bin/imagemap(.*) $1 [PT]
```
### 在多個目錄中搜索頁面
說明:
有時會有必要使web服務器在多個目錄中搜索頁面,對此,MultiViews或者其他技術無能為力。
方案:
編制一個明確的規則集以搜索目錄中的文件。
```
RewriteEngine on
# 首先嘗試在 custom/...中尋找
RewriteCond /your/docroot/**dir1**/%{REQUEST_FILENAME} -f
RewriteRule ^(.+) /your/docroot/**dir1**/$1 [L]
# 然后嘗試在 pub/...中尋找
RewriteCond /your/docroot/**dir2**/%{REQUEST_FILENAME} -f
RewriteRule ^(.+) /your/docroot/**dir2**/$1 [L]
# 再找不到就繼續尋找其他的Alias 或 ScriptAlias 目錄...
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的主頁,但不是用在此機器上建虛擬主機的方法,而是用僅在此機器上增加一個DNS記錄的方法實現。
方案:
對HTTP/1.0的請求,這是無法實現的;但是對HTTP/1.1的在HTTP頭中包含有主機名的請求,可以用以下規則集來內部地重寫http://www.username.host.com/anypath為/home/username/anypath
```
RewriteEngine on
RewriteCond %{**HTTP_HOST**} ^www\.**[^.]+**\.host\.com$
RewriteRule ^(.+) %{HTTP_HOST}$1 [C]
RewriteRule ^www\.**([^.]+)**\.host\.com(.*) /home/**$1**$2
```
### 為外來訪問者重定向用戶主目錄
說明:
對不是來自本地域ourdomain.com的外來訪問者的請求,重定向其用戶主目錄URL到另一個web服務器www.somewhere.com ,有時這種做法也會用在虛擬主機的配置段中。
方案:
只須一個重寫條件:
```
RewriteEngine on
RewriteCond %{REMOTE_HOST} **!^.+\.ourdomain\.com$**
RewriteRule ^(/~.+) http://www.somewhere.com/$1 [R,L]
```
### 重定向失敗的URL到其他web服務器
說明:
如何重寫URL以重定向對web服務器A的失敗請求到服務器B,是一個常見的問題。一般,可以用Perl寫的CGI腳本通過`ErrorDocument`來解決,此外,還有`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
```
這種方法使用了`mod_rewrite`提供的"向前參照"(look-ahead)的功能,是一種對所有URL類型都有效而且安全的方法。但是,對web服務器的性能會有影響,所以如果web服務器有一個強大的CPU,那就用這個方法。而在慢速機器上,可以用第一種方法,或者用性能更好的`ErrorDocument`CGI腳本。
### 擴展的重定向
說明:
有時候,我們會需要更多的對重定向URL的(有關字符轉義機制方面的)控制。通常,Apache內核中的URL轉義函數uri_escape()同時還會對錨(anchor)轉義,即類似"url#anchor"的URL,因此,你不能用`mod_rewrite`對此類URL直接重定向。那么如何實現呢?
方案:
必須用NPH-CGI腳本使它自己重定向,因為對NPH(無須解析的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
##
$| = 1;
$url = $ENV{'PATH_INFO'};
print "HTTP/1.0 302 Moved Temporarily\n";
print "Server: $ENV{'SERVER_SOFTWARE'}\n";
print "Location: $url\n";
print "Content-type: text/html\n";
print "\n";
print "<html>\n";
print "<head>\n";
print "<title>302 Moved Temporarily (EXTENDED)</title>\n";
print "</head>\n";
print "<body>\n";
print "<h1>Moved Temporarily (EXTENDED)</h1>\n";
print "The document has moved <a HREF=\"$url\">here</a>.<p>\n";
print "</body>\n";
print "</html>\n";
##EOF##
```
這是一種可以重定向所有URL類型的方法,包括不被`mod_rewrite`直接支持的類型。所以,還可以這樣重定向"news:newsgroup":
```
RewriteRule ^anyurl xredirect:news:newsgroup
```
注意:無須對上述規則加[R]或[R,L],因為"xredirect:"需要在稍后被其特殊的"管道傳送"規則擴展。
### 文檔訪問的多路復用
說明:
你知道[http://www.perl.com/CPAN](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 -- 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 %{TIME_HOUR}%{TIME_MIN} >0700
RewriteCond %{TIME_HOUR}%{TIME_MIN} <1900
RewriteRule ^foo\.html$ foo.day.html
RewriteRule ^foo\.html$ foo.night.html
```
此例使URLfoo.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=WasHTML:yes]
# rewrite to document.phtml if exists
RewriteCond %{REQUEST_FILENAME}.phtml -f
RewriteRule ^(.*)$ $1.phtml [S=1]
# else reverse the previous basename cutout
RewriteCond %{ENV:WasHTML} ^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 %{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**]
RewriteRule ^foo\.html$ foo.**32**.html [**L**]
```
### 動態鏡像
說明:
假定,需要在我們的名稱空間里加入其他遠程主機的頁面。對FTP服務器,可以用`mirror`程序以在本地機器上維持一個對遠程數據的最新的拷貝;對web服務器,可以用類似的用于HTTP的`webcopy`程序。但這兩種技術都有一個主要的缺點:此本地拷貝必須通過這個程序的執行來更新。所以,比較好的方法是,不采用靜態鏡像,而采用動態鏡像,即在有數據請求時自動更新(遠程主機上更新的數據)。
方案:
為此,使用代理吞吐(<dfn class="calibre27">Proxy Throughput</dfn>)功能(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 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**
```
按你的實際配置,只要對上例稍作調整即可。接著,建立通過代理后臺獲取丟失數據的`mod_rewrite`規則:
```
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`的方案:
1. **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`,則其后繼請求都將被送往`www.foo.com`。但是最終結果是正確的,因為請求的總量的確被分散到各個服務器了
2. **DNS 負載均衡**
一種成熟的基于DNS的負載均衡方法是使用[http://www.stanford.edu/~schemers/docs/lbnamed/lbnamed.html](http://www.stanford.edu/~schemers/docs/lbnamed/lbnamed.html)的`lbnamed`程序,它是一個Perl5程序,帶有若干輔助工具,實現了真正的基于DNS的負載均衡。
3. **代理吞吐循環(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 ^/(.+)$ ${lb:$1} [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等等的處理完全是由其他機器完成的,這個才是要點。
| --- | --- |
4. **硬件/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 "%{%v/%T}t %h -> %{SERVER}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://${server:dynamic}/$1 [S=1]
RewriteRule ^/(.*)$ to://${server:static}/$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`(安全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 %{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`即可(通常由一個cronjob完成)。
### 自動更新的文檔
說明:
建立一個復雜的頁面,能夠在用編輯器寫了一個更新的版本時自動在瀏覽器上得到刷新,這不是很好嗎?這可能嗎?
方案:
這是可行的! 這需要綜合利用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
```
以內部地操控此URL
```
/internal/cgi/apache/nph-refresh?f=/u/foo/bar/page.html
```
接著就是NPH-CGI腳本部分了。雖然,人們常說"將此作為一個練習留給讀者",但我還是給出答案了。
```
#!/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{'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;
}
sub print_http_headers_multipart_next {
print "\n--$bound\n";
}
sub print_http_headers_multipart_end {
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;
}
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,需要提供幾百個虛擬主機,那么這就不是一個最佳的選擇了。
方案:
為此,需要用<dfn class="calibre27">代理吞吐(Proxy Throughput)</dfn>功能(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 "%{VHOST}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 %{REQUEST_URL} !^/commonurl1/.*
RewriteCond %{REQUEST_URL} !^/commonurl2/.*
:
RewriteCond %{REQUEST_URL} !^/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}}]
:
```
## 對訪問的限制
### 阻止Robots
說明:
如何阻止一個完全匿名的robot取得特定網絡區域的頁面?一個`/robots.txt`文件可以包含若干"robot排除協議"的行,但不足以阻止此類robot。
方案:
可以用一個規則集以拒絕對網絡區域`/~quux/foo/arc/`(對一個很深的目錄區域進行列表可能會使服務器產生很大的負載)的訪問。還必須確保僅阻止特定的robot,就是說,僅僅阻止robot訪問主機是不夠的,這樣會同時也阻止了用戶訪問該主機。為此,就需要對HTTP頭的User-Agent信息作匹配。
```
RewriteCond %{HTTP_USER_AGENT} ^**NameOfBadRobot**.*
RewriteCond %{REMOTE_ADDR} ^**123\.45\.67\.[8-9]**$
RewriteRule ^**/~quux/foo/arc/**.+ - [**F**]
```
### 阻止內嵌的圖片
說明:
假設,`http://www.quux-corp.de/~quux/`有一些內嵌圖片的頁面,這些圖片很好,所以就有人用超鏈連到他們自己的頁面中了。由于這樣徒然增加了我們的服務器的流量,因此,我們不愿意這種事情發生。
方案:
雖然,我們不能100%地保護這些圖片不被寫入別人的頁面,但至少可以對發出HTTP Referer頭的瀏覽器加以限制。
```
RewriteCond %{HTTP_REFERER} **!^$**
RewriteCond %{HTTP_REFERER} !^http://www.quux-corp.de/~quux/.*$ [NC]
RewriteRule **.*\.gif$** - [F]
```
```
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !.*/foo-with-gif\.html$
RewriteRule **^inlined-in-foo\.gif$** - [F]
```
### 對主機的拒絕
說明:
如何拒絕一批外部列表中的主機對我們服務器的使用?
方案:
```
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]
```
### 對代理的拒絕
說明:
如何拒絕某個主機或者來自特定主機的用戶使用Apache代理?
方案:
首先,要確保Apache web服務器在編譯時配置文件中`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]
```
### 特殊的認證
說明:
有時候,會需要一種非常特殊的認證,即對一組明確指定的用戶,允許其訪問,而沒有(在使用`mod_authz_host`的基本認證方法時可能會出現的)任何提示。
方案:
可是使用一個重寫條件列表來排除所有的朋友:
```
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"的反射器以反射到任意數量的提交頁面?
方案:
使用這個很巧妙的規則集...
```
RewriteMap deflector txt:/path/to/deflector.map
RewriteCond %{HTTP_REFERER} !=""
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]
```
... 并結合對應的重寫映射地圖:
```
##
## 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/**${quux-map:$1}**
```
```
#!/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/...` ,而事實上,可以把它修改以獲得任何你需要的功能。但是要注意,雖然一般用戶都可以**使用**,可是只有系統管理員才可以**定義**這樣的地圖。
- Apache HTTP Server Version 2.2 文檔 [最后更新:2006年3月21日]
- 版本說明
- 從1.3升級到2.0
- 從2.0升級到2.2
- Apache 2.2 新特性概述
- Apache 2.0 新特性概述
- The Apache License, Version 2.0
- 參考手冊
- 編譯與安裝
- 啟動Apache
- 停止和重啟
- 配置文件
- 配置段(容器)
- 緩沖指南
- 服務器全局配置
- 日志文件
- 從URL到文件系統的映射
- 安全方面的提示
- 動態共享對象(DSO)支持
- 內容協商
- 自定義錯誤響應
- 地址和端口的綁定(Binding)
- 多路處理模塊
- Apache的環境變量
- Apache處理器的使用
- 過濾器(Filter)
- suEXEC支持
- 性能方面的提示
- URL重寫指南
- Apache虛擬主機文檔
- 基于主機名的虛擬主機
- 基于IP地址的虛擬主機
- 大批量虛擬主機的動態配置
- 虛擬主機示例
- 深入研究虛擬主機的匹配
- 文件描述符限制
- 關于DNS和Apache
- 常見問題
- 經常問到的問題
- Apache的SSL/TLS加密
- SSL/TLS高強度加密:緒論
- SSL/TLS高強度加密:兼容性
- SSL/TLS高強度加密:如何...?
- SSL/TLS Strong Encryption: FAQ
- 如何.../指南
- 認證、授權、訪問控制
- CGI動態頁面
- 服務器端包含入門
- .htaccess文件
- 用戶網站目錄
- 針對特定平臺的說明
- 在Microsoft Windows中使用Apache
- 在Microsoft Windows上編譯Apache
- Using Apache With Novell NetWare
- Running a High-Performance Web Server on HPUX
- The Apache EBCDIC Port
- 服務器和支持程序
- httpd - Apache超文本傳輸協議服務器
- ab - Apache HTTP服務器性能測試工具
- apachectl - Apache HTTP服務器控制接口
- apxs - Apache 擴展工具
- configure - 配置源代碼樹
- dbmmanage - 管理DBM格式的用戶認證文件
- htcacheclean - 清理磁盤緩沖區
- htdbm - 操作DBM密碼數據庫
- htdigest - 管理用于摘要認證的用戶文件
- httxt2dbm - 生成RewriteMap指令使用的dbm文件
- htpasswd - 管理用于基本認證的用戶文件
- logresolve - 解析Apache日志中的IP地址為主機名
- rotatelogs - 滾動Apache日志的管道日志程序
- suexec - 在執行外部程序之前切換用戶
- 其他程序
- 雜項文檔
- 與Apache相關的標準
- Apache模塊
- 描述模塊的術語
- 描述指令的術語
- Apache核心(Core)特性
- Apache MPM 公共指令
- Apache MPM beos
- Apache MPM event
- Apache MPM netware
- Apache MPM os2
- Apache MPM prefork
- Apache MPM winnt
- Apache MPM worker
- Apache模塊 mod_actions
- Apache模塊 mod_alias
- Apache模塊 mod_asis
- Apache模塊 mod_auth_basic
- Apache模塊 mod_auth_digest
- Apache模塊 mod_authn_alias
- Apache模塊 mod_authn_anon
- Apache模塊 mod_authn_dbd
- Apache模塊 mod_authn_dbm
- Apache模塊 mod_authn_default
- Apache模塊 mod_authn_file
- Apache模塊 mod_authnz_ldap
- Apache模塊 mod_authz_dbm
- Apache模塊 mod_authz_default
- Apache模塊 mod_authz_groupfile
- Apache模塊 mod_authz_host
- Apache模塊 mod_authz_owner
- Apache模塊 mod_authz_user
- Apache模塊 mod_autoindex
- Apache模塊 mod_cache
- Apache模塊 mod_cern_meta
- Apache模塊 mod_cgi
- Apache模塊 mod_cgid
- Apache模塊 mod_charset_lite
- Apache模塊 mod_dav
- Apache模塊 mod_dav_fs
- Apache模塊 mod_dav_lock
- Apache模塊 mod_dbd
- Apache模塊 mod_deflate
- Apache模塊 mod_dir
- Apache模塊 mod_disk_cache
- Apache模塊 mod_dumpio
- Apache模塊 mod_echo
- Apache模塊 mod_env
- Apache模塊 mod_example
- Apache模塊 mod_expires
- Apache模塊 mod_ext_filter
- Apache模塊 mod_file_cache
- Apache模塊 mod_filter
- Apache模塊 mod_headers
- Apache模塊 mod_ident
- Apache模塊 mod_imagemap
- Apache模塊 mod_include
- Apache模塊 mod_info
- Apache模塊 mod_isapi
- Apache模塊 mod_ldap
- Apache模塊 mod_log_config
- Apache模塊 mod_log_forensic
- Apache模塊 mod_logio
- Apache模塊 mod_mem_cache
- Apache模塊 mod_mime
- Apache模塊 mod_mime_magic
- Apache模塊 mod_negotiation
- Apache模塊 mod_nw_ssl
- Apache模塊 mod_proxy
- Apache模塊 mod_proxy_ajp
- Apache模塊 mod_proxy_balancer
- Apache模塊 mod_proxy_connect
- Apache模塊 mod_proxy_ftp
- Apache模塊 mod_proxy_http
- Apache模塊 mod_rewrite
- Apache模塊 mod_setenvif
- Apache模塊 mod_so
- Apache模塊 mod_speling
- Apache模塊 mod_ssl
- Apache模塊 mod_status
- Apache模塊 mod_suexec
- Apache模塊 mod_unique_id
- Apache模塊 mod_userdir
- Apache模塊 mod_usertrack
- Apache模塊 mod_version
- Apache模塊 mod_vhost_alias
- Developer Documentation for Apache 2.0
- Apache 1.3 API notes
- Debugging Memory Allocation in APR
- Documenting Apache 2.0
- Apache 2.0 Hook Functions
- Converting Modules from Apache 1.3 to Apache 2.0
- Request Processing in Apache 2.0
- How filters work in Apache 2.0
- Apache 2.0 Thread Safety Issues
- 詞匯和索引
- 詞匯表
- 指令索引
- 指令速查
- 模塊索引
- 站點導航