Rewrite Valve

はじめに

Rewrite Valveは、Apache HTTP Serverのmod_rewriteと非常に似た方法でURL書き換え機能を実装します。

設定

Rewrite Valveは、org.apache.catalina.valves.rewrite.RewriteValveクラス名を使用してバルブとして設定されます。

Rewrite Valveは、ホストに追加されたバルブとして設定できます。仮想サーバーのドキュメントを参照して、設定方法を確認してください。書き換えディレクティブを含むrewrite.configファイルを使用します。このファイルは、ホストの設定フォルダに配置する必要があります。

Webアプリケーションの`context.xml`にも配置できます。この場合、バルブは書き換えディレクティブを含む`rewrite.config`ファイルを使用し、このファイルはWebアプリケーションの`WEB-INF`フォルダに配置する必要があります。

ディレクティブ

rewrite.configファイルには、mod_rewriteで使用されるディレクティブと非常によく似たディレクティブのリストが含まれています。特に、中心的な`RewriteRule`と`RewriteCond`ディレクティブです。`#`文字で始まる行はコメントとして扱われ、無視されます。

注:このセクションは、mod_rewriteドキュメントの修正版です。これは、Apache License, Version 2.0に基づいてライセンス供与された、Copyright 1995-2006 The Apache Software Foundationの著作物です。

RewriteCond

構文:RewriteCond TestString CondPattern

RewriteCondディレクティブは、ルール条件を定義します。1つ以上のRewriteCondは、RewriteRuleディレクティブの前に置くことができます。次のルールは、URIの現在の状態がそのパターンに一致し、これらの条件が満たされた場合にのみ使用されます。

TestStringは、プレーンテキストに加えて、以下の展開可能な構成を含むことができる文字列です。

  • RewriteRuleバックリファレンス: これらは$N(0 <= N <= 9)という形式のバックリファレンスであり、現在のRewriteCond条件の対象となるRewriteRuleのパターン内のグループ化された部分(括弧内)にアクセスできます。
  • 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仕様の他の場所で説明されています。Rewrite Valveに特有のものは、以下のとおりです。

    REQUEST_PATH
    マッピングに使用される完全パスに対応します。
    CONTEXT_PATH
    マッピングされたコンテキストのパスに対応します。
    SERVLET_PATH
    サーブレットパスに対応します。
    THE_REQUEST
    ブラウザからサーバーに送信された完全なHTTPリクエスト行(例:「GET /index.html HTTP/1.1」)。ブラウザによって送信された追加ヘッダーは含まれません。
    REQUEST_URI
    HTTPリクエスト行で要求されたリソース。(上記の例では、「/index.html」になります。)
    REQUEST_FILENAME
    リクエストに一致するファイルまたはスクリプトへの完全なローカルファイルシステムパス。
    HTTPS
    接続がSSL/TLSを使用している場合は「on」、それ以外の場合は「off」というテキストが含まれます。

その他注意すべき点

  1. 変数SCRIPT_FILENAMEREQUEST_FILENAMEは同じ値(Apacheサーバーの内部request_rec構造のfilenameフィールドの値)を含みます。最初の名前は一般的に知られているCGI変数名であり、2番目はREQUEST_URIrequest_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. CondPatternにはいくつかの特別なバリアントがあります。実際の正規表現文字列の代わりに、次のいずれかを使用することもできます。
    • <CondPattern」(辞書順で先行する)
      CondPatternをプレーンな文字列として扱い、TestStringと辞書順で比較します。TestStringCondPatternより辞書順で先行する場合、trueになります。
    • >CondPattern」(辞書順で後続する)
      CondPatternをプレーンな文字列として扱い、TestStringと辞書順で比較します。TestStringCondPatternより辞書順で後続する場合、trueになります。
    • =CondPattern」(辞書順で等しい)
      CondPatternをプレーンな文字列として扱い、TestStringと辞書順で比較します。TestStringCondPatternと辞書順で等しい場合(2つの文字列が文字単位で完全に等しい場合)、trueになります。CondPattern""(2つの引用符)の場合、TestStringを空文字列と比較します。
    • -d」(directoryである)
      TestStringをパス名として扱い、それが存在し、ディレクトリであるかどうかをテストします。
    • -f」(通常のfileである)
      TestStringをパス名として扱い、それが存在し、通常のファイルであるかどうかをテストします。
    • -s」(サイズのある通常のファイル)
      TestStringをパス名として扱い、それが存在し、サイズがゼロより大きい通常のファイルであるかどうかをテストします。
    注:これらのテストはすべて、感嘆符(「!」)を前に付けることで、意味を否定できます。
  3. RewriteCondディレクティブの3番目の引数として[flags]を付加することで、CondPatternに特別なフラグを設定することもできます。ここで、flagsは次のフラグのいずれかのカンマ区切りのリストです。
    • nocase|NC」(no case)
      これにより、テストが大文字と小文字を区別しないようになります。展開されたTestStringCondPatternの両方において、「A-Z」と「a-z」の違いは無視されます。このフラグは、TestStringCondPattern間の比較でのみ有効です。ファイルシステムとサブリクエストのチェックには影響しません。
    • ornext|OR」(次の条件をor
      暗黙的なANDではなく、ローカルORを使用してルール条件を組み合わせるために使用します。典型的な例:
      RewriteCond %{REMOTE_HOST}  ^host1.*  [OR]
      RewriteCond %{REMOTE_HOST}  ^host2.*  [OR]
      RewriteCond %{REMOTE_HOST}  ^host3.*
      RewriteRule ...some special stuff for any of these hosts...
      このフラグを使用しない場合、条件/ルールペアを3回記述する必要があります。

リクエストの「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` ルールの最初のパラメータとして指定された名前で登録されます。

注:複数のパラメータを使用できます。パラメータはスペースで区切る必要があります。パラメータは「"」(ダブルクォート)で囲むことができます。これにより、パラメータ内にスペース文字を含めることができます。

そのマップインスタンスには、対応する `RewriteRule` で設定されたルックアップ値が `lookup(String)` を呼び出すことで渡されます。実装では、指定されたデフォルト値を使用する必要があることを示すために `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 ファイルに入れて、`${CATALINA_BASE}/lib` に配置します。

これを行うと、`RewriteMap` ディレクティブを使用してマップを定義し、さらに `RewriteRule` でそのマップを使用できるようになります。

RewriteMap uc example.maps.UpperCaseMap

RewriteRule ^/(.*)$ ${uc:$1}

この設定により、URLパス `/index.html` へのリクエストは `/INDEX.HTML` にルーティングされます。

RewriteRule

構文: `RewriteRule パターン 置換`

RewriteRule ディレクティブは、実際の書き換えの主力です。このディレクティブは複数回出現することができ、それぞれが単一の書き換えルールを定義します。これらのルールの定義順序は重要であり、実行時に適用される順序となります。

パターンは、現在のURLに適用されるPerl互換の正規表現です。「現在」とは、このルールが適用されたときのURLの値を意味します。これは、最初に要求されたURLとは異なる場合があり、既に前のルールに一致して変更されている可能性があります。

**セキュリティに関する警告:** Javaの正規表現の一致方法のため、正しく形成されていない正規表現パターンは、「カタストロフィックバックトラッキング」(「正規表現サービス拒否」またはReDoSとしても知られています)に対して脆弱です。したがって、RewriteRuleパターンには特別な注意が必要です。一般的に、このような脆弱な正規表現を自動的に検出することは困難であるため、効果的な防御策としては、カタストロフィックバックトラッキングに関する情報を少し読むことです。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正規表現など)の詳細に関心がある場合は、次の書籍を参照してください。

Mastering Regular Expressions, 2nd Edition
Jeffrey E.F. Friedl
O'Reilly & Associates, Inc. 2002
ISBN 978-0-596-00289-3

ルールでは、NOT文字(`!`)もパターンのプレフィックスとして使用できます。これにより、パターンを否定することができます。例えば、「現在のURLがこのパターンに**一致しない場合**」のように使用できます。これは、否定的なパターンに一致させる方が容易な例外的なケース、または最後のデフォルトルールとして使用できます。

注:NOT文字を使用してパターンを否定する場合、そのパターンにグループ化されたワイルドカード部分を組み込むことはできません。これは、パターンが一致しない(つまり、否定が一致する)場合、グループの内容がないためです。したがって、否定パターンを使用する場合は、置換文字列で`$N`を使用できません。

書き換えルールの置換とは、パターンに一致した元のURLに代わる(または置き換える)文字列です。プレーンテキストに加えて、以下を含めることができます。

  1. RewriteRuleパターンのバックリファレンス(`$N`)
  2. 最後に一致したRewriteCondパターンのバックリファレンス(`%N`)
  3. ルール条件テスト文字列と同じサーバー変数(`%{VARNAME}`)
  4. マッピング関数呼び出し(`${mapname:key|default}`)

バックリファレンスは、`$`**N**(**N**=0..9)形式の識別子であり、一致したパターンの**N**番目のグループの内容に置き換えられます。サーバー変数は、`RewriteCond`ディレクティブのTestStringと同じです。マッピング関数は`RewriteMap`ディレクティブから取得され、そこで説明されています。これらの3種類の変数は、上記の順序で展開されます。

既に述べたように、すべての書き換えルールは置換に適用されます(設定ファイルで定義されている順序で)。URLは置換によって**完全に置き換えられ**、すべてのルールが適用されるまで、または`**L**`フラグによって明示的に終了されるまで、書き換えプロセスが継続されます。

特殊文字`$`と`%`は、バックスラッシュ文字`\`を前に付けることで引用符で囲むことができます。

「`-`」という特別な置換文字列があり、これは**置換なし**を意味します!これは、URLにのみ一致するが何も置き換えない書き換えルールを提供する際に役立ちます。これは通常、**C**(チェーン)フラグと組み合わせて使用され、置換が発生する前に複数のパターンを適用するために使用されます。

新しいmod_rewriteバージョンとは異なり、Tomcat書き換えバルブは絶対URLを自動的にサポートしていません(絶対URLを指定するには、特定のリダイレクトフラグを使用する必要があります。以下を参照)。または直接ファイルを提供しません。

さらに、`RewriteRule`ディレクティブの第3引数として**`[`flags`]`**を付加することで、Substitutionに特別なフラグを設定できます。Flagsは、以下のフラグのいずれかのカンマ区切りのリストです。

  • `**chain|C**`(次のルールと**c**hain)
    このフラグは、現在のルールを次のルールとチェーンします(それ自体が次のルールとチェーンされる可能性があります)。これには次のような効果があります。ルールが一致する場合は、通常どおり処理が続行されます。フラグは効果がありません。ルールが**一致しない**場合は、後続のすべてのチェーンされたルールがスキップされます。例えば、外部リダイレクトが発生する場合(「`.www`」部分が含まれていない場合)、ディレクトリごとのルールセット内で「`.www`」部分を削除するために使用できます。
  • `**cookie|CO=**`_NAME_:_VAL_:_domain_[:_lifetime_[:_path_]]`(**co**okieを設定)
    これにより、クライアントのブラウザにcookieが設定されます。cookieの名前は_NAME_で指定され、値は_VAL_です。_domain_フィールドはcookieのドメイン(「.apache.org」など)であり、オプションの_lifetime_はcookieの有効期間(分)、オプションの_path_はcookieのパスです。
  • `**env|E=**`_VAR_:_VAL_`(**e**nvironment変数を設定)
    これにより、_VAR_という名前のリクエスト属性が_VAL_の値に設定されます。ここで_VAL_には、展開される正規表現のバックリファレンス(`$N`および`%N`)を含めることができます。このフラグを複数回使用して、複数の変数を設定できます。
  • `**forbidden|F**`(URLを強制的に**f**orbiddenにする)
    これにより、現在のURLが禁止されます。403(FORBIDDEN)のHTTP応答をすぐに返します。適切なRewriteCondsと組み合わせてこのフラグを使用し、一部のURLを条件付きでブロックします。
  • `**gone|G**`(URLを強制的に**g**oneにする)
    これにより、現在のURLがgoneになります。410(GONE)のHTTP応答をすぐに返します。もはや存在しないページをgoneとしてマークするために、このフラグを使用します。
  • `**host|H**`=_Host_`(**h**ostに書き換えを適用)
    URLを書き換えるのではなく、仮想ホストが書き換えられます。
  • `**last|L**`(**l**astルール)
    ここで書き換えプロセスを停止し、それ以上の書き換えルールを適用しません。これは、Perlの`last`コマンドまたはCの`break`コマンドに対応します。このフラグを使用して、現在の書き換えられたURLが後続のルールによってさらに書き換えられないようにします。例えば、ルートパスURL(`/`)を実際のURL(例:`/e/www/`)に書き換えるために使用します。
  • `**next|N**`(**n**extラウンド)
    書き換えプロセスを再実行します(最初の書き換えルールから再開します)。今回は、一致するURLは元のURLではなく、最後の書き換えルールによって返されたURLになります。これは、Perlの`next`コマンドまたはCの`continue`コマンドに対応します。このフラグを使用して、書き換えプロセスを再開し、ループの先頭にすぐに移動します。
    無限ループを作成しないように注意してください!
  • nocase|NC」(no case)
    これにより、パターンが大文字と小文字を区別しないようになり、パターンが現在のURLと一致するときに「A-Z」と「a-z」の違いを無視します。
  • `**noescape|NE**`(出力のURIエスケープを**n**o)
    このフラグは、書き換えバルブが書き換えの結果に通常のURIエスケープルールを適用するのを防ぎます。通常、特殊文字(「%」、「$」、「;」など)は、対応する16進コード(それぞれ「%25」、「%24」、「%3B」)にエスケープされます。このフラグはこれを防ぎます。これにより、パーセント記号を出力に含めることができます。例:
    RewriteRule /foo/(.*) /bar?arg=P1\%3d$1 [R,NE]
    これにより、「`/foo/zed`」が「`/bar?arg=P1=zed`」の安全なリクエストになります。
  • `**qsappend|QSA**`(**q**uery **s**tring **a**ppend)
    このフラグは、書き換えエンジンに、置換文字列のクエリ文字列部分を既存の文字列に置き換えるのではなく、追加するように強制します。書き換えルールを使用してクエリ文字列にデータを追加する場合に使用します。
  • `**redirect|R**` [=_code_]`( **r**edirectを強制)
    外部リダイレクトを強制するために、プレフィックス`Substitution`に`http://thishost[:thisport]/` (これにより新しいURLがURIになります)を追加します。`code`が指定されていない場合、HTTPレスポンス302(FOUND、以前はMOVED TEMPORARILY)が返されます。300~399の範囲の他のレスポンスコードを使用する場合は、適切な数値を指定するか、以下のシンボリック名を使用してください:`temp`(デフォルト)、`permanent`、`seeother`。この機能は、URLを正規化してクライアントに返すルールに使用します - 例として「`/~`」を「`/u/`」に変換する、または「`/u/`」*user*に常にスラッシュを追加するなど。
    注記:このフラグを使用する場合は、置換フィールドが有効なURLであることを確認してください!そうでない場合、無効な場所にリダイレクトされます。このフラグだけでは`http://thishost[:thisport]/` がURLの先頭に付加されるだけで、書き換えは継続されることに注意してください。通常は、この時点で書き換えを停止し、すぐにリダイレクトしたいでしょう。書き換えを停止するには、「L」フラグを追加する必要があります。
  • `skip|S=num`(次のルールをスキップ)
    このフラグは、現在のルールが一致した場合、書き換えエンジンに次の`num`個のルールを順番にスキップさせます。擬似的なif-then-else構造を作成するために使用します。then句の最後のルールは`skip=N`になり、Nはelse句内のルールの数です。(これは`chain|C`フラグとは**異なります**!)
  • `type|T=MIME-type`(MIMEタイプを強制)
    ターゲットファイルのMIMEタイプを`MIME-type`に強制します。これは、いくつかの条件に基づいてコンテンツタイプを設定するために使用できます。たとえば、次のスニペットでは、`.phps`拡張子で呼び出された場合、`.php`ファイルが`mod_php`によって*表示*されるようになります。
    RewriteRule ^(.+\.php)s$ $1 [T=application/x-httpd-php-source]
  • `valveSkip|VS`(バルブをスキップ)
    このフラグは、バルブの条件付き実行を設定するために使用できます。フラグが設定されていてルールが一致すると、書き換えバルブはCatalinaパイプライン内の次のバルブをスキップします。書き換えバルブがパイプラインの最後にある場合、フラグは無視され、コンテナの基本バルブが呼び出されます。書き換えが行われた場合、このフラグは効果を持ちません。