介绍

和 Apache HTTP Server 中 mod_rewrite 非常相似的方式实现 URL 重写功能。

配置

重写 valve 使用 org.apache.catalina.valves.rewrite.RewriteValve 类名配置。

重写 valve 可以配置为在 Host 中添加的 valve。 有关如何配置的信息,请参阅 virtual-server 文档。 将使用包含 rewrite 指令的 rewrite.config 文件,必须将其放在 Host 配置文件夹中。

也可以位于 Web 应用程序的 context.xml 中。 然后,valve 将使用包含 rewrite 指令的 rewrite.config 文件,该文件必须放在 Web 应用程序的 WEB-INF 文件夹中。

指令

rewrite.config 文件包含一个指令列表,这些指令与 mod_rewrite 使用的指令非常相似, 特别是位于中心的 RewriteRuleRewriteCond 指令。 以 # 字符开头的行将被视为注释,将被忽略。

注意:本部分是 mod_rewrite 文档的修改版本, 其版权归 1995-2006 The Apache Software Foundation 所有,并根据 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 的文档。

  • Server-Variables:这些是 %{ NAME_OF_VARIABLE } 形式的变量,其中 NAME_OF_VARIABLE 可以是取自以下列表中的字符串:

    • HTTP headers:

HTTP_USER_AGENT
HTTP_REFERER
HTTP_COOKIE
HTTP_FORWARDED
HTTP_HOST
HTTP_PROXY_CONNECTION
HTTP_ACCEPT
  • connection & request:

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
  • server internals:

DOCUMENT_ROOT
SERVER_NAME
SERVER_ADDR
SERVER_PORT
SERVER_PROTOCOL
SERVER_SOFTWARE
  • date and time:

TIME_YEAR
TIME_MON
TIME_DAY
TIME_HOUR
TIME_MIN
TIME_SEC
TIME_WDAY
TIME
  • specials:

THE_REQUEST
REQUEST_URI
REQUEST_FILENAME
HTTPS

这些变量都对应于名称相似的 HTTP MIME 标头和 Servlet API 方法。 大多数都记录在手册或 CGI 规范中的其他位置。 重写 valve 特有的包括以下那些。

  • 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_rec 的 uri 字段的值)。

  2. %{ENV:variable},其中 variable 可以是任何 Java 系统属性。

  3. %{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。

  4. %{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' (是目录)将 TestString 视为路径名并测试它是否存在,并且它是一个目录。

    • '-f' (是常规文件)将 TestString 视为路径名并测试它是否存在,并且它是一个常规文件。

    • '-s' (是常规文件,大小为)将 TestString 视为路径名并测试它是否存在,并且是大小大于零的常规文件。

注意:所有这些测试也可以以感叹号 ('!') 为前缀来否定它们的含义。

  • 还可以通过将 [flags] 作为第三个参数附加到 RewriteCond 指令来为 CondPattern 设置特殊标志,其中 flags 是以下任何标志的逗号分隔列表:

    • 'nocase|NC'(无大小写) 这使得测试不区分大小写 - 在展开的 TestString 和 CondPattern 中,“A-Z”和“a-z”之间的差异都被忽略。此标志仅对 TestString 和 CondPattern 之间的比较有效。它对文件系统和子请求检查没有影响。

    • 'ornext|OR'(或下一个条件) 使用此选项将规则条件与本地 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...

如果没有此标志,则必须将条件/规则对写入 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 等), 那么将获得 max 主页(可能包括框架或其他特殊功能)。 如果使用 Lynx 浏览器(基于终端),那么将获得 min 主页(这可能是为简单的纯文本浏览而设计的版本)。 如果这两个条件都不适用(使用任何其他浏览器,或者浏览器将自己标识为非标准浏览器), 将获得 std (standard) 主页。

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) 将通过调用 setParameters(String) 使用可选参数 (上面的 optionalParameters)进行实例化和初始化(注意空格)。 然后,该实例将注册在作为 RewriteMap 规则的第一个参数的名称下。

注: 可以使用多个参数。这些必须用空格分隔。 参数可以用 “.这将在参数中启用空格字符。

通过调用 lookup(String),将为该 map 实例提供在相应的 RewriteRule 中配置的 lookup 值。 实现可以自由返回 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 指令定义一个 map, 并进一步在 RewriteRule 中使用该 map。

RewriteMap uc example.maps.UpperCaseMap
RewriteRule ^/(.*)$ ${uc:$1}

通过此设置,对 url 路径 /index.html 的请求将路由到 /INDEX.HTML。

RewriteRule

语法:RewriteRule 模式替换

RewriteRule 指令是真正的重写主力。 该指令可以多次出现,每个实例定义一个重写规则。 定义这些规则的顺序很重要 - 这是它们在运行时的应用顺序。

Pattern 是 perl 兼容的正则表达式,它应用于当前 URL。 “当前”是指应用此规则时 URL 的值。 这可能不是最初请求的 URL,该 URL 可能已与之前的规则匹配,并且已更改。

安全警告:由于 Java 的正则表达式匹配方式, 格式不正确的正则表达式模式容易受到“灾难性回溯”的影响, 也称为“正则表达式拒绝服务”或 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 正则表达式等)的更多详细信息感兴趣, 请参阅以下专门讨论此主题的书:

掌握正则表达式,第 2 版

Jeffrey E.F. Friedl

O’Reilly & Associates, Inc. 2002

ISBN 978-0-596-00289-3

在规则中,NOT 字符 ('!') 也可用作可能的模式前缀。 这使能够否定模式;例如,说:“如果当前 URL 与此模式不匹配”。 这可用于特殊情况,在这种情况下,更容易匹配负模式,或作为最后一个默认规则。

注: 使用 NOT 字符否定模式时,不能在该模式中包含分组的通配符部分。 这是因为,当模式不匹配(即否定匹配)时,组没有内容。 因此,如果使用了否定模式,则不能在替换字符串中使用 $N!

重写规则的替换是替换(或替换)匹配的原始 URL 的字符串 Pattern。 除了纯文本之外,它还可以包括:

  1. 反向引用 ($N) 到 RewriteRule 模式

  2. 反向引用 (%N) 到最后一个匹配的 RewriteCond 模式

  3. server-variables,如规则条件 test-strings (%{VARNAME})

  4. 映射函数调用 (${mapname:key|default})

反向引用是 $N (N=0..9) 形式的标识符, 将被匹配的 Pattern 的第 N 组的内容替换。 server-variables 与 RewriteCond 指令的 TestString 相同。 映射函数来自 RewriteMap 指令,并在那里进行了解释。这三种类型的变量按上面的顺序展开。

如前所述,所有重写规则都应用于 Substitution(按照它们在配置文件中定义的顺序)。 URL 将完全替换为 Substitution,并且重写过程将继续,直到应用了所有规则,或者由 L 标志显式终止。

特殊字符 $ 和 % 可以通过在它们前面加上反斜杠字符 来引用。

有一个名为 '-' 的特殊替换字符串,意思是:NO substitution! 这在提供仅匹配 URL 但不替换 URL 的任何内容的重写规则时非常有用。 它通常与 C (chain) 标志结合使用,以便在替换发生之前应用多个模式。

与较新的 mod_rewrite 版本不同,Tomcat 重写valve不会自动支持绝对 URL (必须使用特定的重定向标志才能指定绝对 URL,见下文)或直接文件服务。

此外,可以通过将 [flags] 作为第三个参数附加到 RewriteRule 指令中 来为 Substitution 设置特殊标志。Flags 是以下任何标志的逗号分隔列表:

  • 'chain|C' (与下一条规则链接) 此标志将当前规则与下一个规则链接在一起(该规则本身可以与以下规则链接,依此类推)。这具有以下效果:如果规则匹配,则处理照常继续 - 该标志无效。如果规则不匹配,则跳过所有后续的链式规则。例如,当您允许外部重定向发生时,它可用于删除每个目录规则集中的 '.www' 部分(其中 '.www' 部分不应该出现!

  • 'cookie|CO=NAME:VAL:domain[:lifetime[:p ath]]' (设置 Cookie) 这将在客户端的浏览器中设置一个 Cookie。Cookie 的名称由 NAME 指定,值为 VAL。domain 字段是 cookie 的域,例如 '.apache.org',可选的生命周期是 cookie 的生命周期(以分钟为单位),可选路径是 cookie 的路径

  • 'env|E=VAR:VAL' (设置环境变量) 这会强制将名为 VAR 的请求属性设置为值 VAL,其中 VAL 可以包含将扩展的正则表达式反向引用($N 和 %N)。您可以多次使用此标志来设置多个变量。

  • 'forbidden|F'(强制禁止 URL) 这会强制禁止当前 URL - 它会立即发回 403 (FORBIDDEN) 的 HTTP 响应。将此标志与适当的 RewriteConds 结合使用,以有条件地阻止某些 URL。

  • 'gone|G'(强制 URL 消失) 这会强制当前 URL 消失 - 它会立即发回 410 (GONE) 的 HTTP 响应。使用此标志将不再存在的页面标记为已消失。

  • 'host|H=Host' (对主机应用重写) 虚拟主机将被重写,而不是重写 URL。

  • 'last|L'(最后一条规则) 在此处停止重写过程,不再应用任何重写规则。这对应于 C 中的 Perl last 命令或 break 命令。使用此标志可防止当前重写的 URL 被遵循规则进一步重写。例如,使用它来将根路径 URL ('/') 重写为真实的 URL,例如 '/e/www/'。

  • 'next|N' (下一轮) 重新运行重写过程(从第一个重写规则重新开始)。这一次,要匹配的 URL 不再是原始 URL,而是上次重写规则返回的 URL。这对应于 C 中的 Perl next 命令或 continue 命令。使用此标志重新启动重写过程 - 立即转到循环的顶部。

小心不要创建无限循环!

  • 'nocase|NC' (无大小写) 这使得 Pattern 不区分大小写,当 Pattern 与当前 URL 匹配时,会忽略 'A-Z' 和 'a-z' 之间的差异。

  • 'noescape|NE'(没有 URI 转义输出) 此标志可防止 rewrite valve 将通常的 URI 转义规则应用于重写的结果。通常,特殊字符(例如 '%'、'$'、';' 等)将被转义为它们的十六进制代码等效项(分别为 '%25'、'%24' 和 '%3B');此标志可防止这种情况发生。这允许百分比符号出现在输出中,如

RewriteRule /foo/(.*) /bar?arg=P1\%3d$1 [R,NE]

这会将 '/foo/zed' 变成对 '/bar?arg=P1=zed' 的安全请求。

  • 'qsappend|QSA' (查询字符串附加) 此标志强制重写引擎将替换字符串的查询字符串部分附加到现有字符串,而不是替换它。当您想通过 rewrite 规则向查询字符串添加更多数据时,请使用此选项。

  • 'redirect|R [=code]' (强制重定向) 用 :thisport/ 替换前缀(使新 URL 成为 URI)以强制外部重定向。如果未提供代码,则将返回 HTTP 响应 302(FOUND,以前为 MOVED TEMPORARILY)。如果要使用 300-399 范围内的其他响应代码,只需指定适当的数字或使用以下符号名称之一:temp (default)、permanent、seeother。将此函数用于规则,将 URL 规范化并将其返回给客户端 - 将 '/~' 转换为 '/u/',或始终向 /u/user 附加斜杠,等等。

注意:使用此标记时,请确保 substitution 字段是有效的 URL!否则,您将重定向到无效的位置。请记住,此标志本身只会在 URL 前面加上 :thisport/,并且重写将继续。通常,此时您需要停止重写,并立即重定向。要停止重写,应该添加 'L' 标志。

  • 'skip|S=num' (跳过下一个规则) 如果当前规则匹配,则此标志会强制重写引擎按顺序跳过下一个 num 规则。使用它来制作伪 if-then-else 结构:then-clause 的最后一条规则变为 skip=N,其中 N 是 else 子句中的规则数。(这与 'chain|C' 标志!不同)

  • 'type|T=MIME-type' (强制 MIME 类型) 强制目标文件的 MIME 类型为 MIME 类型。这可用于根据某些条件设置 content-type。例如,以下代码段允许 mod_php .php 文件(如果使用 .phps 扩展名调用)显示这些文件:

RewriteRule ^(.+\.php)s$ $1 [T=application/x-httpd-php-source]
  • 'valveSkip|VS' (跳阀) 此标志可用于设置 valve 的条件执行。当设置标志并且规则匹配时,重写阀将跳过 Catalina 管道中的下一个阀。如果 rewrite valve 是管道的最后一个,则将忽略该标志并调用容器 basic valve。如果发生 rewrite,则标志将不起作用。