重寫 Valve 以與 Apache HTTP Server 的 mod_rewrite 非常類似的方式實作 URL 重寫功能。
重寫 Valve
簡介
設定
重寫 Valve 使用 org.apache.catalina.valves.rewrite.RewriteValve
類別名稱設定為 Valve。
重寫 Valve 可以設定為在主機中新增的 Valve。請參閱 虛擬伺服器 文件,以取得如何設定它的資訊。它將使用包含重寫指令的 rewrite.config
檔案,必須放置在主機設定資料夾中。
也可以在 Webapp 的 context.xml 中。然後,閥值將使用包含重寫指令的 rewrite.config
檔案,它必須放置在 Web 應用程式的 WEB-INF 資料夾中
指令
rewrite.config 檔案包含指令清單,這些指令與 mod_rewrite 使用的指令非常類似,特別是中央 RewriteRule 和 RewriteCond 指令。以 #
字元開頭的行會被視為註解,並將被忽略。
注意:本節是 mod_rewrite 文件的修改版本,其版權為 1995-2006 Apache 軟體基金會所有,並根據 Apache 授權版本 2.0 授權。
RewriteCond
語法:RewriteCond TestString CondPattern
RewriteCond 指令定義規則條件。一個或多個 RewriteCond 可以出現在 RewriteRule 指令之前。然後,僅當 URI 的目前狀態與其模式相符,並且符合這些條件時,才會使用下列規則。
TestString 是可以包含以下擴充結構的字串,除了純文字之外
-
RewriteRule 反向參考:這些是形式為
$N
(0 <= N <= 9) 的反向參考,它們提供存取模式的分組部分(在括號中),來自RewriteRule
,它受當前RewriteCond
條件集約束。 -
RewriteCond 反向參考:這些是形式為
%N
(1 <= N <= 9) 的反向參考,它們提供存取模式的分組部分(同樣在括號中),來自當前條件集中最後匹配的RewriteCond
。 -
RewriteMap 擴充:這些是形式為
${mapname:key|default}
的擴充。有關更多詳細資訊,請參閱 RewriteMap 文件。 -
伺服器變數:這些是形式為
%{
變數名稱}
的變數,其中 變數名稱 可以是取自下列清單的字串-
HTTP 標頭
HTTP_USER_AGENT
HTTP_REFERER
HTTP_COOKIE
HTTP_FORWARDED
HTTP_HOST
HTTP_PROXY_CONNECTION
HTTP_ACCEPT
-
連線與要求
REMOTE_ADDR
REMOTE_HOST
REMOTE_PORT
REMOTE_USER
REMOTE_IDENT
REQUEST_METHOD
SCRIPT_FILENAME
REQUEST_PATH
CONTEXT_PATH
SERVLET_PATH
PATH_INFO
QUERY_STRING
AUTH_TYPE
-
伺服器內部
DOCUMENT_ROOT
SERVER_NAME
SERVER_ADDR
SERVER_PORT
SERVER_PROTOCOL
SERVER_SOFTWARE
-
日期和時間
TIME_YEAR
TIME_MON
TIME_DAY
TIME_HOUR
TIME_MIN
TIME_SEC
TIME_WDAY
TIME
-
特殊
THE_REQUEST
REQUEST_URI
REQUEST_FILENAME
HTTPS
這些變數都對應到類似名稱的 HTTP MIME 標頭和 Servlet API 方法。大部分都已在手冊或 CGI 規範的其他地方有說明。那些對重寫閥特殊的部分包括以下。
REQUEST_PATH
- 對應到用於對應的完整路徑。
CONTEXT_PATH
- 對應到對應的內容路徑。
SERVLET_PATH
- 對應到 servlet 路徑。
THE_REQUEST
- 瀏覽器傳送給伺服器的完整 HTTP 要求列(例如,"
GET /index.html HTTP/1.1
")。這不包含瀏覽器傳送的任何其他標頭。 REQUEST_URI
- HTTP 要求列中要求的資源。(在上面的範例中,這會是 "/index.html"。)
REQUEST_FILENAME
- 與要求相符的檔案或指令碼的完整本機檔案系統路徑。
HTTPS
- 如果連線使用 SSL/TLS,將包含文字 "on",否則包含 "off"。
-
您應該注意的其他事項
- 變數 SCRIPT_FILENAME 和 REQUEST_FILENAME 包含相同的值 - Apache 伺服器的內部
request_rec
結構的filename
欄位的數值。第一個名稱是大家熟知的 CGI 變數名稱,而第二個名稱是 REQUEST_URI 的適當對應部分(包含request_rec
的uri
欄位的數值)。 -
%{ENV:variable}
,其中 variable 可以是任何 Java 系統屬性,也可用。 -
%{SSL:variable}
,其中 variable 是 SSL 環境變數的名稱,已實作,但SSL_SESSION_RESUMED
、SSL_SECURE_RENEG
、SSL_COMPRESS_METHOD
、SSL_TLS_SNI
、SSL_SRP_USER
、SSL_SRP_USERINFO
、SSL_CLIENT_VERIFY
、SSL_CLIENT_SAN_OTHER_msUPN_n
、SSL_CLIENT_CERT_RFC4523_CEA
、SSL_SERVER_SAN_OTHER_dnsSRV_n
除外。當使用 OpenSSL 時,與伺服器憑證相關的變數,其字首為SSL_SERVER_
,則不可用。範例:%{SSL:SSL_CIPHER_USEKEYSIZE}
可能會擴充為128
。 -
%{HTTP:header}
,其中 header 可以是任何 HTTP MIME 標頭名稱,可用於取得在 HTTP 要求中傳送的標頭值。範例:%{HTTP:Proxy-Connection}
是 HTTP 標頭 'Proxy-Connection:
' 的值。
CondPattern 是條件模式,套用於 TestString 的目前執行個體的正規表示式。TestString 會先經過評估,然後才與 CondPattern 相符。
請記住:CondPattern 是 與 perl 相容的正規表示式,並增加了一些功能
- 您可以使用 '
!
' 字元 (驚嘆號) 為模式字串加上字首,以指定不相符的模式。 -
有一些 CondPatterns 的特殊變體。您可以使用下列其中一項,取代真正的正規表示式字串
- '<CondPattern' (字彙順序較早)
將 CondPattern 視為純文字字串,並與 TestString 進行字彙順序比較。如果 TestString 字彙順序較早於 CondPattern,則為 True。 - '>CondPattern' (字彙順序較晚)
將 CondPattern 視為純文字字串,並與 TestString 進行字彙順序比較。如果 TestString 字彙順序較晚於 CondPattern,則為 True。 - '=CondPattern' (字彙順序相等)
將 CondPattern 視為純文字字串,並與 TestString 進行字彙順序比較。如果 TestString 字彙順序與 CondPattern 相等 (兩個字串完全相等,每個字元都相同),則為 True。如果 CondPattern 為""
(兩個引號),則會將 TestString 與空字串進行比較。 - '-d' (為directory)
將 TestString 視為路徑名稱,並測試其是否存在,且為目錄。 - '-f' (為一般file)
將 TestString 視為路徑名稱,並測試其是否存在,且為一般檔案。 - '-s' (為一般檔案,有size)
將 TestString 視為路徑名稱,並測試其是否存在,且為一般檔案,且大小大於 0。
- '<CondPattern' (字彙順序較早)
- 您也可以為 CondPattern 設定特殊旗標,方法是將
[
flags]
附加為RewriteCond
指令的第三個引數,其中 flags 是下列任何旗標的逗號分隔清單- '
nocase|NC
' (no case)
這會讓測試不區分大小寫 - 'A-Z' 和 'a-z' 之間的差異會被忽略,無論是在擴充的 TestString 還是 CondPattern 中。此標記只會對 TestString 和 CondPattern 之間的比較有效。它對檔案系統和子請求檢查沒有影響。 -
'
ornext|OR
' (or next condition)
使用此標記來結合規則條件,使用區域 OR 而不是隱含的 AND。典型的範例沒有此標記,你必須寫三次條件/規則配對。RewriteCond %{REMOTE_HOST} ^host1.* [OR] RewriteCond %{REMOTE_HOST} ^host2.* [OR] RewriteCond %{REMOTE_HOST} ^host3.* RewriteRule ...some special stuff for any of these hosts...
- '
範例
要根據請求的 'User-Agent:
' 標頭改寫網站的首頁,你可以使用下列內容
RewriteCond %{HTTP_USER_AGENT} ^Mozilla.*
RewriteRule ^/$ /homepage.max.html [L]
RewriteCond %{HTTP_USER_AGENT} ^Lynx.*
RewriteRule ^/$ /homepage.min.html [L]
RewriteRule ^/$ /homepage.std.html [L]
說明:如果你使用瀏覽器自我識別為 'Mozilla'(包括 Netscape Navigator、Mozilla 等),那麼你會取得最大首頁(可能包含框架或其他特殊功能)。如果你使用 Lynx 瀏覽器(基於終端機),那麼你會取得最小首頁(可能是一個設計為易於瀏覽的純文字版本)。如果這些條件都不適用(你使用任何其他瀏覽器,或你的瀏覽器自我識別為非標準的),你會取得標準首頁。
RewriteMap
語法:RewriteMap name rewriteMapClassName optionalParameters
rewriteMapClassName
值也允許特殊值
int:toupper
:將傳遞值轉換為大寫的特殊映射int:tolower
:將傳遞值轉換為小寫的特殊映射int:escape
:URL 編碼傳遞值int:unescape
:URL 解碼傳遞值
這些映射是使用使用者必須實作的介面來實作的。其類別名稱是 org.apache.catalina.valves.rewrite.RewriteMap
,其程式碼如下
package org.apache.catalina.valves.rewrite;
public interface RewriteMap {
default String setParameters(String params...); // calls setParameters(String) with the first parameter if there is only one
public String setParameters(String params);
public String lookup(String key);
}
此類別的參考實作 - 在我們的範例中為 rewriteMapClassName
- 將會使用選用參數 - 上述的 optionalParameters
(小心空白) - 透過呼叫 setParameters(String)
來實例化和初始化。然後會將該實例註冊在 RewriteMap
規則的第一個參數所提供的名稱下。
注意:你可以使用多個參數。這些參數必須用空格分隔。參數可以用 " 引用。這允許參數中出現空白字元。
該映射實例將會透過呼叫 lookup(String)
提供在對應的 RewriteRule
中設定的查詢值。你的實作可以自由傳回 null
來表示應使用提供的預設值,或傳回取代值。
假設您想要實作一個重寫對應函式,將所有查詢金鑰轉換為大寫。您會從實作一個實作 RewriteMap
介面的類別開始。
package example.maps;
import org.apache.catalina.valves.rewrite.RewriteMap;
public class UpperCaseMap implements RewriteMap {
@Override
public String setParameters(String params) {
// nothing to be done here
return null;
}
@Override
public String lookup(String key) {
if (key == null) {
return null;
}
return key.toUpperCase();
}
}
編譯此類別,將它放入 jar 中,並將該 jar 放置在 ${CATALINA_BASE}/lib
中。
完成此步驟後,您現在可以使用 RewriteMap
指令定義一個對應,並進一步在 RewriteRule
中使用該對應。
RewriteMap uc example.maps.UpperCaseMap
RewriteRule ^/(.*)$ ${uc:$1}
使用此設定,對 url 路徑 /index.html
的要求會路由到 /INDEX.HTML
。
RewriteRule
語法:RewriteRule Pattern Substitution
RewriteRule 指令是真正的重寫主力。該指令可以出現多次,每個執行個體定義單一重寫規則。定義這些規則的順序很重要,因為這是它們在執行時套用的順序。
Pattern 是與 Perl 相容的正規表示式,套用於目前的 URL。'目前' 表示套用此規則時的 URL 值。這可能不是最初要求的 URL,因為它可能已經符合先前的規則,並已被變更。
安全性警告:由於 Java 的 regex 比對方式,格式不佳的 regex 模式容易受到「災難性回溯」的影響,也稱為「正規表示式阻斷服務」或 ReDoS。因此,應對 RewriteRule 模式特別小心。一般來說,很難自動偵測到這種容易受攻擊的 regex,因此一個好的防禦方式是稍微閱讀一下災難性回溯的主題。一個很好的參考是 OWASP ReDoS 指南。
正規表示式語法的提示
Text:.
Any single character[
chars]
Character class: Any character of the class 'chars'[^
chars]
Character class: Not a character of the class 'chars' text1|
text2 Alternative: text1 or text2 Quantifiers:?
0 or 1 occurrences of the preceding text*
0 or N occurrences of the preceding text (N > 0)+
1 or N occurrences of the preceding text (N > 1) Grouping:(
text)
Grouping of text (used either to set the borders of an alternative as above, or to make backreferences, where the Nth group can be referred to on the RHS of a RewriteRule as$
N) Anchors:^
Start-of-line anchor$
End-of-line anchor Escaping:\
char escape the given char (for instance, to specify the chars ".[]()
" etc.)
如需有關正規表示式的更多資訊,請參閱 perl 正規表示式手冊頁(「perldoc perlre」)。如果您有興趣取得有關正規表示式及其變體(POSIX regex 等)的更詳細資訊,下列書籍專門探討這個主題
Mastering Regular Expressions, 2nd Edition
Jeffrey E.F. Friedl
O'Reilly & Associates, Inc. 2002
ISBN 978-0-596-00289-3
在規則中,NOT 字元('!
')也可用作可能的模式前置詞。這讓您可以否定模式;例如說:'如果目前的 URL 不符合此模式'。這可用於例外情況,在這種情況下,比對負面模式比較容易,或作為最後的預設規則。
注意:當使用 NOT 字元否定模式時,您不能在該模式中包含群組萬用字元部分。這是因為,當模式不符合時(即否定符合時),群組沒有內容。因此,如果使用否定模式,您不能在替換字串中使用 $N
!
重寫規則的 替換是替換(或取代)Pattern 所符合的原始 URL 的字串。除了純文字外,它還可以包含
- 回溯參考(
$N
)至 RewriteRule 模式 - 反向參照 (
%N
) 至最後比對的 RewriteCond 樣式 - 伺服器變數如同規則條件測試字串 (
%{VARNAME}
) - 對應函數 呼叫 (
${mapname:key|default}
)
反向參照是 $
N (N=0..9) 形式的識別碼,它會被比對的 樣式 第 N 群組的內容取代。伺服器變數與 RewriteCond
指令的 TestString 相同。對應函數來自 RewriteMap
指令,並在其中說明。這三種類型的變數會按上述順序展開。
如前所述,所有改寫規則都會套用至 取代 (依據在設定檔中定義的順序)。網址會被 取代 完全取代,而且改寫程序會持續進行,直到套用所有規則,或由 L
旗標明確終止為止。
特殊字元 $
和 %
可以透過在前面加上反斜線字元 \
來引用。
有一個名為 '-
' 的特殊取代字串,其意為:不取代!這對於提供僅比對網址,但不會為其取代任何內容的改寫規則很有用。它通常與 C (鏈結) 旗標結合使用,以便在取代發生之前套用多個樣式。
與較新的 mod_rewrite 版本不同,Tomcat 改寫閥門不會自動支援絕對網址 (必須使用特定的重新導向旗標才能指定絕對網址,請見下方) 或直接檔案提供。
此外,您可以透過將 [
flags]
附加為 RewriteRule
指令的第三個引數,為 取代 設定特殊的 旗標。旗標 是下列任一旗標的逗號分隔清單
- '
chain|C
' (與下一個規則鏈結)
此旗標會將目前的規則與下一個規則鏈結 (下一個規則本身也可以與下一個規則鏈結,依此類推)。這會有以下效果:如果規則比對成功,則處理會照常進行 - 旗標沒有效果。如果規則未比對成功,則會略過所有後續的鏈結規則。例如,當您讓外部重新導向發生 (其中不應出現 '.www
' 部分) 時,它可以用於移除目錄規則組內部的 '.www
' 部分。 -
'
cookie|CO=
NAME:VAL:domain[:lifetime[:path]]' (設定cookie)
這會在客戶端瀏覽器中設定 cookie。cookie 的名稱由 NAME 指定,而值為 VAL。domain 欄位是 cookie 的網域,例如 '.apache.org',選用的 lifetime 是 cookie 的使用期限 (以分鐘為單位),而選用的 path 是 cookie 的路徑 -
'
env|E=
VAR:VAL'(設定environment 變數)
這會強制將名為 VAR 的請求屬性設定為值 VAL,其中 VAL 可以包含正規表示法反向參照($N
和%N
),這些反向參照將會展開。您可以使用此旗標多次,以設定多個變數。 - '
forbidden|F
'(強制 URL 為forbidden)
這會強制目前的 URL 為 forbidden - 它會立即傳回 HTTP 回應 403(FORBIDDEN)。將此旗標與適當的 RewriteConds 結合使用,以有條件地封鎖某些 URL。 - '
gone|G
'(強制 URL 為gone)
這會強制目前的 URL 為 gone - 它會立即傳回 HTTP 回應 410(GONE)。使用此旗標將不再存在的頁面標記為已消失。 -
'
host|H
=Host'(將重寫套用至host)
虛擬主機將會被重寫,而不是 URL。 - '
last|L
'(last 規則)
在此停止重寫程序,不再套用任何重寫規則。這對應於 Perllast
指令或 C 中的break
指令。使用此旗標可防止目前重寫的 URL 被後續規則進一步重寫。例如,使用它將根路徑 URL('/
')重寫為真實的 URL,例如,'/e/www/
'。 - '
next|N
'(next 回合)
重新執行重寫程序(從第一個重寫規則重新開始)。這次,要比對的 URL 不再是原始 URL,而是最後一個重寫規則傳回的 URL。這對應於 Perlnext
指令或 C 中的continue
指令。使用此旗標可重新啟動重寫程序 - 立即回到迴圈的頂端。
小心不要建立無限迴圈! - '
nocase|NC
' (no case)
這會讓 Pattern 忽略大小寫,在將 Pattern 與目前的 URL 比對時,忽略 'A-Z' 和 'a-z' 之間的差異。 -
'
noescape|NE
'(輸出時no URI escaping)
此旗標可防止重寫閥門將通常的 URI escaping 規則套用至重寫的結果。通常,特殊字元(例如 '%', '$', ';' 等)會被 escaping 成它們的十六進位碼等價字元(分別為 '%25', '%24' 和 '%3B');此旗標可防止這種情況發生。這允許百分比符號出現在輸出中,就像將 'RewriteRule /foo/(.*) /bar?arg=P1\%3d$1 [R,NE]
/foo/zed
' 變成 '/bar?arg=P1=zed
' 的安全請求。 - '
qsappend|QSA
'(query string append)
此旗標強制重寫引擎將取代字串的查詢字串部分附加到現有字串,而不是取代它。當您想要透過重寫規則將更多資料新增到查詢字串時,請使用此旗標。 - '
redirect|R
[=code]'(強制 redirect)
以http://thishost[:thisport]/
(這會讓新的 URL 變成 URI)作為前綴替換,以強制執行外部重新導向。如果沒有提供代碼,將會傳回 HTTP 回應 302(已找到,先前為暫時已搬移)。如果您想在 300-399 範圍內使用其他回應代碼,只需指定適當的數字或使用下列符號名稱之一:temp
(預設)、permanent
、seeother
。對規則使用這個來標準化 URL 並將其傳回給客戶端,例如將 '/~
' 轉換為 '/u/
',或始終將斜線附加到/u/
user 等。
注意:當您使用這個標記時,請確定替換欄位是有效的 URL!否則,您會重新導向到無效的位置。請記住,這個標記本身只會在 URL 前加上http://thishost[:thisport]/
,而且會繼續改寫。通常,您會希望在這個時候停止改寫,並立即重新導向。若要停止改寫,您應該加上 'L' 標記。 - '
skip|S
=num'(skip 下一個規則)
如果目前的規則相符,這個標記會強制改寫引擎略過接下來順序中的 num 個規則。使用這個來建立偽 if-then-else 結構:then 子句的最後一個規則會變成skip=N
,其中 N 是 else 子句中的規則數目。(這不同於 'chain|C' 標記!) -
'
type|T
=MIME 類型'(強制 MIME type)
強制目標檔案的 MIME 類型為 MIME 類型。這可以用於根據某些條件設定內容類型。例如,以下片段允許.php
檔案在使用.phps
副檔名呼叫時由mod_php
顯示RewriteRule ^(.+\.php)s$ $1 [T=application/x-httpd-php-source]
- '
valveSkip|VS
'(略過閥門)
這個標記可以用於設定閥門的條件式執行。當設定標記且規則相符時,改寫閥門會略過 Catalina 管線中的下一個閥門。如果改寫閥門是管線中的最後一個,則會忽略這個標記,並會呼叫容器基本閥門。如果發生改寫,則這個標記不會有任何效果。