重寫 Valve

簡介

重寫 Valve 以與 Apache HTTP Server 的 mod_rewrite 非常類似的方式實作 URL 重寫功能。

設定

重寫 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"。

您應該注意的其他事項

  1. 變數 SCRIPT_FILENAME 和 REQUEST_FILENAME 包含相同的值 - Apache 伺服器的內部 request_rec 結構的 filename 欄位的數值。第一個名稱是大家熟知的 CGI 變數名稱,而第二個名稱是 REQUEST_URI 的適當對應部分(包含 request_recuri 欄位的數值)。
  2. %{ENV:variable},其中 variable 可以是任何 Java 系統屬性,也可用。
  3. %{SSL:variable},其中 variable 是 SSL 環境變數的名稱,已實作,但 SSL_SESSION_RESUMEDSSL_SECURE_RENEGSSL_COMPRESS_METHODSSL_TLS_SNISSL_SRP_USERSSL_SRP_USERINFOSSL_CLIENT_VERIFYSSL_CLIENT_SAN_OTHER_msUPN_nSSL_CLIENT_CERT_RFC4523_CEASSL_SERVER_SAN_OTHER_dnsSRV_n 除外。當使用 OpenSSL 時,與伺服器憑證相關的變數,其字首為 SSL_SERVER_,則不可用。範例:%{SSL:SSL_CIPHER_USEKEYSIZE} 可能會擴充為 128
  4. %{HTTP:header},其中 header 可以是任何 HTTP MIME 標頭名稱,可用於取得在 HTTP 要求中傳送的標頭值。範例:%{HTTP:Proxy-Connection} 是 HTTP 標頭 'Proxy-Connection:' 的值。

CondPattern 是條件模式,套用於 TestString 的目前執行個體的正規表示式。TestString 會先經過評估,然後才與 CondPattern 相符。

請記住:CondPattern與 perl 相容的正規表示式,並增加了一些功能

  1. 您可以使用 '!' 字元 (驚嘆號) 為模式字串加上字首,以指定相符的模式。
  2. 有一些 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。
    注意:所有這些測試都可以使用驚嘆號 ('!') 為字首,以否定其意義。
  3. 您也可以為 CondPattern 設定特殊旗標,方法是將 [flags] 附加為 RewriteCond 指令的第三個引數,其中 flags 是下列任何旗標的逗號分隔清單
    • 'nocase|NC' (no case)
      這會讓測試不區分大小寫 - 'A-Z' 和 'a-z' 之間的差異會被忽略,無論是在擴充的 TestString 還是 CondPattern 中。此標記只會對 TestStringCondPattern 之間的比較有效。它對檔案系統和子請求檢查沒有影響。
    • '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 的字串。除了純文字外,它還可以包含

  1. 回溯參考($N)至 RewriteRule 模式
  2. 反向參照 (%N) 至最後比對的 RewriteCond 樣式
  3. 伺服器變數如同規則條件測試字串 (%{VARNAME})
  4. 對應函數 呼叫 (${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 指定,而值為 VALdomain 欄位是 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 規則)
    在此停止重寫程序,不再套用任何重寫規則。這對應於 Perl last 指令或 C 中的 break 指令。使用此旗標可防止目前重寫的 URL 被後續規則進一步重寫。例如,使用它將根路徑 URL('/')重寫為真實的 URL,例如,'/e/www/'。
  • 'next|N'(next 回合)
    重新執行重寫程序(從第一個重寫規則重新開始)。這次,要比對的 URL 不再是原始 URL,而是最後一個重寫規則傳回的 URL。這對應於 Perl next 指令或 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(預設)、permanentseeother。對規則使用這個來標準化 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 管線中的下一個閥門。如果改寫閥門是管線中的最後一個,則會忽略這個標記,並會呼叫容器基本閥門。如果發生改寫,則這個標記不會有任何效果。