URL重寫:RewriteCond指令與RewriteRule 指令格式

Rewirte主要的功能就是實現URL的跳轉和隱藏真實地址,基於Perl語言的正則表達式規範。平時幫助我們實現擬靜態,擬目錄,域名跳轉,防止盜鏈等。本文將針對mod_rewrite和URL匹配的技術細節,以及RewriteCond與RewriteRule 指令格式進行探討。

Rewirte模塊內部處理

Rewirte模塊的內部處理極爲複雜,但是爲了使一般用戶避免犯低級錯誤,也讓管理員能充分利用其功能,在此仍然做一下說明。

Rewirte模塊API階段

首先,你必須瞭解Apache是分若干階段來處理HTTP請求的。Apache API對每個階段都提供了一個hook程序。mod_rewrite使用兩個hook程序:其一,從URL到文件名的轉換hook(用在讀取HTTP請求之後、授權開始之前); 其二,修正hook(用在授權階段和讀取目錄級配置(.htaccess)之後、內容處理器激活之前)。

所以,Apache收到一個請求並且確定了響應主機(或虛擬主機)之後,重寫引擎即開始處理服務器級配置中的所有mod_rewrite指令(此時處於從URL到文件名轉換的階段),此階段完成後,最終的數據目錄便確定了。接下來進入修正程序段並觸發目錄級配置中的mod_rewrite指令。這兩個階段並不是涇渭分明的,但都實施了把URL重寫成新的URL或者文件名。雖然API最初不是爲此目的而設計的,但是現在它已經成爲了API的一種用途。記住以下兩點,會有助於更好地理解:

1、雖然mod_rewrite可以將URL重寫爲新的URL或文件名,甚至將文件名重寫爲新的文件名,但是之前的API只提供從URL到文件名的hook。在Apache 2.0中,增加了兩個丟失的hook以使得處理過程更加清晰。不過這樣做並沒有給用戶帶來麻煩,用戶只需記住這樣一個事實:藉助從URL到文件名的hook比最初API設計的目標功能更強大。

2、令人難以置信的是,mod_rewrite還提供了目錄級的URL操作(.htaccess文件),而這些文件必須在將URL轉換成文件名之後纔會被處理(這是必須的,因爲.htaccess存在於文件系統中)。換句話說,根據API階段,這時再處理任何URL操作已經太晚了。爲了解決這個”雞和蛋”的問題,mod_rewrite使用了一個小技巧:在進行一個目錄級的URL/文件名操作時,先把文件名重寫回相應的URL(通常這個操作是不可行的,但是參考下面的RewriteBase指令就能明白它是怎麼實現的了),然後,對這個新的URL建立一個新的內部的子請求,再重新開始API階段的執行。

另外,mod_rewrite盡力使這些複雜的操作對用戶透明。但仍須記住:服務器級的URL操作速度快而且效率高,而目錄級的操作由於這個”雞和蛋”的問題速度較慢而且效率也低。但從另一個側面看,這卻是mod_rewrite得以爲一般用戶提供(局部限制的)URL操作的唯一方法。

Rewirte模塊規則集的處理

當mod_rewrite在這兩個API階段中開始執行時,它會讀取配置結構中配置好的 (或者是在服務啓動時建立的服務器級的,或者是在遍歷目錄採集到的目錄級的)規則集,然後,啓動URL重寫引擎來處理(帶有一個或多個條件的)規則集。無論是服務器級的還是目錄級的規則集,都是由同一個URL重寫引擎處理,只是最終結果處理不同而已。

規則集中規則的順序是很重要的,因爲重寫引擎是按一種特殊的順序處理的:逐個遍歷每個規則(RewriteRule指令),如果出現一個匹配條件的規則,則可能回頭遍歷已有的規則條件(RewriteCond指令)。由於歷史的原因,條件規則是前置的,所以控制流程略顯冗長,細節見圖-1。


圖-1:重寫規則集中的控制流

 

可見,URL首先與每個規則的Pattern匹配,如果匹配失敗,mod_rewrite將立即終止此規則的處理,繼而處理下一個規則。如果匹配成功,mod_rewrite將尋找相應的規則條件,如果一個條件都沒有,則簡單地用Substitution構造的新值來替換URL,然後繼續處理其他規則;但是如果條件存在,則開始一個內部循環按其列出的順序逐個處理。對規則條件的處理有所不同:URL並不與模式進行匹配,而是首先通過擴展變量、反向引用、查找映射表等步驟建立一個TestString字符串,然後用它來與CondPattern匹配。如果匹配失敗,則整個條件集和對應的規則失敗;如果匹配成功,則執行下一個規則直到所有條件執行完畢。如果所有條件得以匹配,則以Substitution替換URL,並且繼續處理。(本部分引用譯者:金步國)

RewriteCond指令格式

語法: RewriteCond TestString CondPattern [flags]

RewriteCond指令定義一條規則條件。在一條RewriteRule指令前面可能會有一條或多條RewriteCond指令,只有當自身的模板(pattern)匹配成功且這些條件也滿足時規則才被應用於當前URL處理。

1、 TestString是一個純文本的字符串,除了包含普通的字符外,還可以包括下列的可擴展結構:

1)$N:RewriteRule後向引用,其中(0 <= N <= 9) 。$N引用緊跟在RewriteCond後面的RewriteRule中模板中的括號中的模板在當前URL中匹配的數據。

2)%N:RewriteCond後向引用,其中(0 <= N <= 9) 。%N引用最後一個RewriteCond的模板中的括號中的模板在當前URL中匹配的數據。

3)${mapname:key|default}:RewriteMap擴展。

2、CondPattern是條件pattern, 即一個應用於當前實例TestString的正則表達式, 即TestString將會被計算然後與CondPattern匹配。作爲一個標準的擴展正則式,CondPattern有以下補充:

1)可以在模板串前增加一個!前綴,以用表示不匹配模板。但並不是所有的test都可以加!前綴。

2)CondPattern中可以使用以下特殊變量:

'>CondPattern’ (大於) 將condPattern當作一個普通字符串,將它和TestString進行比較,當TestString 的字符大於CondPattern爲真。

‘=CondPattern’ (等於) 將condPattern當作一個普通字符串,將它和TestString進行比較,當TestString 與CondPattern完全相同時爲真.如果CondPattern只是 “” (兩個引號緊挨在一起) 此時需TestString 爲空字符串方爲真。

‘-d’ (是否爲目錄) 將testString當作一個目錄名,檢查它是否存在以及是否是一個目錄。

‘-f’ (是否是regular file) 將testString當作一個文件名,檢查它是否存在以及是否是一個regular文件。

‘-s’ (是否爲長度不爲0的regular文件) 將testString當作一個文件名,檢查它是否存在以及是否是一個長度大於0的regular文件。

‘-l’ (是否爲symbolic link) 將testString當作一個文件名,檢查它是否存在以及是否是一個 symbolic link。

‘-F’ (通過subrequest來檢查某文件是否可訪問) 檢查TestString是否是一個合法的文件,而且通過服務器範圍內的當前設置的訪問控制進行訪問。這個檢查是通過一個內部subrequest完成的, 因此需要小心使用這個功能以降低服務器的性能。

‘-U’ (通過subrequest來檢查某個URL是否存在) 檢查TestString是否是一個合法的URL,而且通過服務器範圍內的當前設置的訪問控制進行訪問。這個檢查是通過一個內部subrequest完成的, 因此需要小心使用這個功能以降低服務器的性能。

3、[flags]是第三個參數,多個標誌之間用逗號分隔。

1)’nocase|NC’ (不區分大小寫)   在擴展後的TestString和CondPattern中,比較時不區分文本的大小寫。注意,這個標誌對文件系統和subrequest檢查沒有影響.

2)’ornext|OR’ (建立與下一個條件的或的關係)   默認的情況下,二個條件之間是AND的關係,用這個標誌將關係改爲OR。例如: RewriteCond %{REMOTE_HOST} ^host1.* [OR] RewriteCond %{REMOTE_HOST} ^host2.* [OR] RewriteCond %{REMOTE_HOST} ^host3.* RewriteRule … 如果沒有[OR]標誌,需要寫三個條件/規則.

RewriteRule 指令

語法: RewriteRule Pattern Substitution [flags]

1) Pattern是一個作用於當前URL的兼容perl的正則表達式. 這裏的“當前”是指該規則生效時的URL的值。

2) Substitution是,當原始URL與Pattern相匹配時,用以替代(或替換)的字符串。

3) 此外,Substitution還可以追加特殊標記[flags] 作爲RewriteRule指令的第三個參數。 Flags是一個包含以逗號分隔的下列標記的列表:

redirect|R [=code] (強制重定向 redirect)

以 http://thishost[:thisport]/(使新的URL成爲一個URI) 爲前綴的Substitution可以強制性執行一個外部重定向。 如果code沒有指定,則產生一個HTTP響應代碼302(臨時性移動)。如果需要使用在300-400範圍內的其他響應代碼,只需在此指定這個數值即可, 另外,還可以使用下列符號名稱之一: temp (默認的), permanent, seeother. 用它可以把規範化的URL反饋給客戶端,如, 重寫“/~”爲 “/u/”,或對/u/user加上斜槓,等等。

注意: 在使用這個標記時,必須確保該替換字段是一個有效的URL! 否則,它會指向一個無效的位置! 並且要記住,此標記本身只是對URL加上 http://thishost[:thisport]/的前綴,重寫操作仍然會繼續。通常,你會希望停止重寫操作而立即重定向,則還需要使用’L’標記.

forbidden|F (強制URL爲被禁止的 forbidden)

強制當前URL爲被禁止的,即,立即反饋一個HTTP響應代碼403(被禁止的)。使用這個標記,可以鏈接若干RewriteConds以有條件地阻塞某些URL。

gone|G’(強制URL爲已廢棄的 gone)

強制當前URL爲已廢棄的,即,立即反饋一個HTTP響應代碼410(已廢棄的)。使用這個標記,可以標明頁面已經被廢棄而不存在了.

proxy|P (強制爲代理 proxy)

此標記使替換成分被內部地強制爲代理請求,並立即(即, 重寫規則處理立即中斷)把處理移交給代理模塊。你必須確保此替換串是一個有效的(比如常見的以 http://hostname開頭的)能夠爲Apache代理模塊所處理的URI。使用這個標記,可以把某些遠程成分映射到本地服務器名稱空間, 從而增強了ProxyPass指令的功能。

注意: 要使用這個功能,代理模塊必須編譯在Apache服務器中。 如果你不能確定,可以檢查“httpd -l”的輸出中是否有mod_proxy.c。 如果有,則mod_rewrite可以使用這個功能;如果沒有,則必須啓用mod_proxy並重新編譯“httpd”程序。

last|L (最後一個規則 last)

立即停止重寫操作,並不再應用其他重寫規則。 它對應於Perl中的last命令或C語言中的break命令。這個標記可以阻止當前已被重寫的URL爲其後繼的規則所重寫。 舉例,使用它可以重寫根路徑的URL(’/’)爲實際存在的URL, 比如, ‘/e/www/’.

next|N (重新執行 next round)

重新執行重寫操作(從第一個規則重新開始). 這時再次進行處理的URL已經不是原始的URL了,而是經最後一個重寫規則處理的URL。它對應於Perl中的next命令或C語言中的continue命令。 此標記可以重新開始重寫操作,即, 立即回到循環的頭部。
但是要小心,不要製造死循環!

chain|C (與下一個規則相鏈接 chained)

此標記使當前規則與下一個(其本身又可以與其後繼規則相鏈接的, 並可以如此反覆的)規則相鏈接。 它產生這樣一個效果: 如果一個規則被匹配,通常會繼續處理其後繼規則, 即,這個標記不起作用;如果規則不能被匹配,則其後繼的鏈接的規則會被忽略。比如,在執行一個外部重定向時, 對一個目錄級規則集,你可能需要刪除“.www” (此處不應該出現“.www”的)。

type|T=MIME-type(強制MIME類型 type)

強制目標文件的MIME類型爲MIME-type。 比如,它可以用於模擬mod_alias中的ScriptAlias指令,以內部地強制被映射目錄中的所有文件的MIME類型爲“application/x-httpd-cgi”。

nosubreq|NS (僅用於不對內部子請求進行處理 no internal sub-request)

在當前請求是一個內部子請求時,此標記強制重寫引擎跳過該重寫規則。比如,在mod_include試圖搜索可能的目錄默認文件(index.xxx)時, Apache會內部地產生子請求。對子請求,它不一定有用的,而且如果整個規則集都起作用,它甚至可能會引發錯誤。所以,可以用這個標記來排除某些規則。

根據你的需要遵循以下原則: 如果你使用了有CGI腳本的URL前綴,以強制它們由CGI腳本處理,而對子請求處理的出錯率(或者開銷)很高,在這種情況下,可以使用這個標記。

nocase|NC (忽略大小寫 no case)

它使Pattern忽略大小寫,即, 在Pattern與當前URL匹配時,’A-Z’ 和’a-z’沒有區別。

qsappend|QSA (追加請求串 query string append)

此標記強制重寫引擎在已有的替換串中追加一個請求串,而不是簡單的替換。如果需要通過重寫規則在請求串中增加信息,就可以使用這個標記。

noescape|NE (在輸出中不對URI作轉義 no URI escaping)

此標記阻止mod_rewrite對重寫結果應用常規的URI轉義規則。 一般情況下,特殊字符(如’%’, ‘$’, ‘;’等)會被轉義爲等值的十六進制編碼。 此標記可以阻止這樣的轉義,以允許百分號等符號出現在輸出中,如:

RewriteRule /foo/(.*) /bar?arg=P1\=$1 [R,NE] 可以使’/foo/zed’轉向到一個安全的請求’/bar?arg=P1=zed’.

passthrough|PT (移交給下一個處理器 pass through)

此標記強制重寫引擎將內部結構request_rec中的uri字段設置爲 filename字段的值,它只是一個小修改,使之能對來自其他URI到文件名翻譯器的 Alias,ScriptAlias, Redirect 等指令的輸出進行後續處理。舉一個能說明其含義的例子:如果要通過mod_rewrite的重寫引擎重寫/abc爲/def,然後通過mod_alias使/def轉變爲/ghi,可以這樣:

RewriteRule ^/abc(.*) /def$1 [PT]

Alias /def /ghi
如果省略了PT標記,雖然mod_rewrite運作正常, 即, 作爲一個使用API的URI到文件名翻譯器,它可以重寫uri=/abc/…爲filename=/def/…,但是,後續的mod_alias在試圖作URI到文件名的翻譯時,則會失效。

注意: 如果需要混合使用不同的包含URI到文件名翻譯器的模塊時, 就必須使用這個標記。。混合使用mod_alias和mod_rewrite就是個典型的例子。

For Apache hackers

如果當前Apache API除了URI到文件名hook之外,還有一個文件名到文件名的hook, 就不需要這個標記了! 但是,如果沒有這樣一個hook,則此標記是唯一的解決方案。 Apache Group討論過這個問題,並在Apache 2.0 版本中會增加這樣一個hook。

skip|S=num (跳過後繼的規則 skip)

此標記強制重寫引擎跳過當前匹配規則後繼的num個規則。 它可以實現一個僞if-then-else的構造: 最後一個規則是then從句,而被跳過的skip=N個規則是else從句. (它和’chain|C’標記是不同的!)

env|E=VAR:VAL (設置環境變量 environment variable)

此標記使環境變量VAR的值爲VAL, VAL可以包含可擴展的反向引用的正則表達式$N和%N。 此標記可以多次使用以設置多個變量。這些變量可以在其後許多情況下被間接引用,但通常是在XSSI (via ) or CGI (如 $ENV{’VAR’})中, 也可以在後繼的RewriteCond指令的pattern中通過%{ENV:VAR}作引用。使用它可以從URL中剝離並記住一些信息。

cookie|CO=NAME:VAL:domain[:lifetime[:path]] (設置cookie)

它在客戶端瀏覽器上設置一個cookie。 cookie的名稱是NAME,其值是VAL。 domain字段是該cookie的域,比如’.apache.org’, 可選的lifetime是cookie生命期的分鐘數,可選的path是cookie的路徑。

案例:

city_map.txt的內容:

hangzhou 12

beijing 13

1、hangzhou.google.com/tianqi/20090401 跳轉到 www.google.com/service/detail.html?id=tianqi&date=20090401

  1. RewriteMap city-map txt:/etc/httpd/conf.d/map/city_map.txt   
  2.     
  3. RewriteCond %{HTTP_HOST}    ^(.+)\.google\.com$   
  4.     
  5. RewriteRule ^/([\w]+)/([\d]+)$ /service/detail\.html\?id=$1&date=$2&c=${city-map:%1|%1} [PT,L]  


 

 

解釋:

%{HTTP_HOST}:取請求的域名

^(.+)\.google\.com$:^,開頭;$結尾。.(逗號),除終止符外的任意字符。+,重複一個或一個以上的字符。\,轉義字符。

^/([\w]+)/([\d]+)$:[],集合字符。\w,數字或字母。\d,數字。

$1:表示的是符合RewriteRule 中[\w]+正則式的字符串,也就是tianqi。

$2:表示的是符合RewriteRule 中[\d]+ 正則式的字符串,也就是20090401。

%1:表示的是符合RewriteCond 中.+正則式的字符串,也就是hangzhou。

${city-map:%1|%1}:表示取city-map中%1也就是hangzhou對應的值,如果沒有則爲%1也就是hangzhou。

2、能看出下面的規則是做了什麼嗎?

  1. RewriteCond     %{HTTP_HOST}    ^(.+)\.google\.com$   
  2.     
  3. RewriteRule ^/([\w]+)/([^-]+)-([^-]+)--([^-]+)-([^-]+)--([^-]+)-([^-]+)--([^-]+-[^-]+--[^-]+-[^-]+--[^-]+-[^-]+)$  /$1/$2=$3&$4=$5&$6=$7&$8   [C]    
  4.     
  5. RewriteCond     %{HTTP_HOST}    ^(.+)\.google\.com$   
  6.     
  7. RewriteRule ^/([\w]+)/([^-]+)-([^-]+)--([^-]+)-([^-]+)--([^-]+)-([^-]+)$     /service/list\.html\?frontCategoryId=${category-map:$1|0}&$2=$3&$4=$5&$6=$7&city=${city-map:%1|%1}   [PT,L]  


 

解釋:

這個規則是想把-(中劃線)轉爲=,把- -(兩條中劃線)轉爲&。

[^-]:^在字符集合符號([])之內表示反向選擇,之外表示行首,所以表示不以-開頭。

因爲$N,N最大爲9,所以使用了C,用第二條RewriteRule把第一條RewriteRule中的最後一個節點,即$8,進行繼續轉換。

此外,rewrite規則中如果遇到中文,相當有可能會出現亂碼問題,因爲apache在rewrite時會做一次url解碼,這時jk進行請求轉發時,就不會再是編碼後的字符串了。此種情況,可以在一開始就進行兩次編碼(encode),或者在接收請求時先用ISO-8859-1取字節流,再使用UFT-8來new String。(new String(str.getBytes(”ISO-8859-1″),”UTF-8
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章