重写阀

简介

重写阀实现了与Apache HTTP Server的mod_rewrite非常相似的URL重写功能。

配置

重写阀通过使用org.apache.catalina.valves.rewrite.RewriteValve类名被配置为一个阀。

重写阀可以被配置为添加到主机(Host)中的一个阀。有关如何配置它的信息,请参阅虚拟服务器文档。它将使用一个包含重写指令的rewrite.config文件,该文件必须放置在主机的配置文件夹中。

它也可以位于Web应用的context.xml文件中。此时,该阀将使用一个包含重写指令的rewrite.config文件,该文件必须放置在Web应用的WEB-INF文件夹中。

使用带有特殊字符的重写规则

呈现给重写阀的URL是用于请求映射的URL,其中任何字面量'%'';'和/或'?'字符都以%nn的形式编码。

希望插入字面量'%'';''?''&''='字符的重写规则应以%nn的形式进行。其他字符可以以字面量形式或%nn形式插入。

这使得重写规则能够:

  • 处理包含字面量'?'字符的URL;
  • 添加查询字符串;
  • 插入字面量'%'字符,而不会与%nn编码混淆。

指令

rewrite.config文件包含一系列指令,这些指令与mod_rewrite使用的指令非常相似,特别是核心的RewriteRule和RewriteCond指令。以#字符开头的行将被视为注释并被忽略。

注意:本节是mod_rewrite文档的修改版本,该文档版权归Apache Software Foundation所有(1995-2006),并根据Apache许可证2.0版授权。

RewriteCond

语法:RewriteCond TestString CondPattern

RewriteCond指令定义了一个规则条件。一个或多个RewriteCond可以位于RewriteRule指令之前。只有当URI的当前状态与其模式匹配且这些条件得到满足时,随后的规则才会被使用。

TestString是一个字符串,除了纯文本之外,还可以包含以下扩展构造:

  • RewriteRule反向引用:这些是$N(0 <= N <= 9)形式的反向引用,它们提供对受当前RewriteCond条件集约束的RewriteRule模式中分组部分(括号内)的访问。
  • RewriteCond反向引用:这些是%N(1 <= N <= 9)形式的反向引用,它们提供对当前条件集中最后一个匹配的RewriteCond模式中分组部分(同样在括号内)的访问。
  • RewriteMap扩展:这些是${mapname:key|default}形式的扩展。有关详细信息,请参阅RewriteMap文档
  • 服务器变量:这些是%{ NAME_OF_VARIABLE }形式的变量,其中NAME_OF_VARIABLE可以是取自以下列表的字符串:
    • 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之前,则为真。
    • >CondPattern”(字典序在后)
      CondPattern视为一个纯字符串,并与TestString进行字典序比较。如果TestString在字典序上位于CondPattern之后,则为真。
    • =CondPattern”(字典序相等)
      CondPattern视为一个纯字符串,并与TestString进行字典序比较。如果TestString在字典序上与CondPattern相等(两个字符串完全相同,逐个字符),则为真。如果CondPattern""(两个引号),则将TestString与空字符串进行比较。
    • -d”(是directory,即目录)
      TestString视为一个路径名,并测试它是否存在以及是否是一个目录。
    • -f”(是普通file,即文件)
      TestString视为一个路径名,并测试它是否存在以及是否是一个普通文件。
    • -s”(是带size,即大小的普通文件)
      TestString视为一个路径名,并测试它是否存在以及是否是一个大小大于零的普通文件。
    注意:所有这些测试也可以通过加上感叹号('!')前缀来否定其含义。
  3. 您还可以通过将[flags]作为RewriteCond指令的第三个参数来设置CondPattern的特殊标志,其中flags是以下任意标志的逗号分隔列表:
    • nocase|NC”(区分小写)
      这使得测试不区分大小写——在扩展的TestStringCondPattern中,'A-Z'和'a-z'之间的差异被忽略。此标志仅在TestStringCondPattern之间的比较中有效。它对文件系统和子请求检查没有影响。
    • 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...
      如果没有此标志,您将不得不编写三次条件/规则对。

示例

要根据请求的“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(标准)主页。

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)被赋予在相应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(Locale.ENGLISH);
  }

}

编译此文件,将其打包成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,该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 regex等)的更详细信息感兴趣,以下书籍专门探讨此主题:

精通正则表达式,第2版
Jeffrey E.F. Friedl
O'Reilly & Associates, Inc. 2002
ISBN 978-0-596-00289-3

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

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

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

  1. 对RewriteRule模式的反向引用($N
  2. 对最后匹配的RewriteCond模式的反向引用(%N
  3. 规则条件测试字符串中的服务器变量(%{VARNAME}
  4. 映射函数调用(${mapname:key|default}

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

如前所述,所有重写规则都应用于替换字符串(按照它们在配置文件中定义的顺序)。URL被替换字符串完全替换,重写过程持续进行,直到所有规则都已应用,或者由L标志明确终止。

特殊字符$%可以通过在其前面加上反斜杠字符\进行引用。

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

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

此外,您还可以通过将[flags]作为RewriteRule指令的第三个参数,为替换字符串设置特殊标志Flags是以下任意标志的逗号分隔列表:

  • chain|C”(与下一条规则接)
    此标志将当前规则与下一条规则链接(下一条规则本身可以与随后的规则链接,依此类推)。这会产生以下效果:如果一条规则匹配,则处理照常继续——该标志没有效果。如果规则匹配,则所有后续链接规则都将被跳过。例如,当您允许外部重定向发生时(其中不应出现“.www”部分),它可以在每个目录的规则集中用于删除“.www”部分。
  • cookie|CO=NAME:VAL:domain[:lifetime[:path]]”(设置cookie,即Cookie)
    这会在客户端浏览器中设置一个Cookie。Cookie的名称由NAME指定,值为VALdomain字段是Cookie的域,例如“.apache.org”,可选的lifetime是Cookie的生命周期(以分钟为单位),可选的path是Cookie的路径。
  • env|E=VAR:VAL”(设置environment variable,即环境变量)
    这强制将名为VAR的请求属性设置为值VAL,其中VAL可以包含将被扩展的正则表达式反向引用($N%N)。您可以多次使用此标志,以设置多个变量。
  • forbidden|F”(强制URL被forbidden,即禁止)
    这强制当前URL被禁止——它会立即返回一个HTTP 403(FORBIDDEN)响应。结合适当的RewriteConds使用此标志,可以有条件地阻止某些URL。
  • gone|G”(强制URLgone,即已失效)
    这强制当前URL失效——它会立即返回一个HTTP 410(GONE)响应。使用此标志将不再存在的页面标记为已失效。
  • host|H=Host”(将重写应用于host,即主机)
    虚拟主机将被重写,而不是重写URL。
  • last|L”(last rule,即最后一条规则)
    在此处停止重写过程,不再应用任何其他重写规则。这相当于Perl的last命令或C语言中的break命令。使用此标志可以防止当前重写的URL被后续规则进一步重写。例如,用它将根路径URL('/')重写为真实的URL,例如'/e/www/'。
  • next|N”(next round,即下一轮)
    重新运行重写过程(从第一条重写规则开始)。这一次,要匹配的URL不再是原始URL,而是上一条重写规则返回的URL。这相当于Perl的next命令或C语言中的continue命令。使用此标志可以重新启动重写过程——立即回到循环的顶部。
    小心不要创建无限循环!
  • nocase|NC”(区分小写)
    这使得Pattern不区分大小写,在将Pattern与当前URL匹配时,忽略'A-Z'和'a-z'之间的差异。
  • noescape|NE”(输出进行URI转义
    此标志可防止重写阀将常用的URI转义规则应用于重写结果。通常,特殊字符(如“%”、“$”、“;”等)会被转义为其十六进制代码等效形式(分别为“%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),以强制进行外部重定向。如果未给出code,将返回HTTP 302(FOUND,以前是MOVED TEMPORARILY)响应。如果您想使用300-399范围内的其他响应代码,只需指定相应的数字或使用以下符号名称之一:temp(默认)、permanentseeother。使用此标志来规范化URL并将其返回给客户端——例如将“/~”转换为“/u/”,或者总是将斜杠附加到/u/user等等。
    注意:当您使用此标志时,请确保替换字段是一个有效的URL!否则,您将重定向到无效位置。请记住,此标志本身只会将http://thishost[:thisport]/前置到URL,并且重写将继续。通常,您会希望在此处停止重写并立即重定向。要停止重写,您应该添加“L”标志。
  • skip|S=num”(skip next rule(s),即跳过接下来的规则)
    如果当前规则匹配,此标志强制重写引擎跳过序列中接下来的num条规则。使用此功能可以创建伪if-then-else结构:then子句的最后一条规则变为skip=N,其中N是else子句中规则的数量。(这与“chain|C”标志同!)
  • type|T=MIME-type”(强制MIMEtype,即类型)
    强制目标文件的MIME类型为MIME-type。这可用于根据某些条件设置内容类型。例如,以下代码片段允许.php文件在被.phps扩展名调用时,由mod_php进行显示
    RewriteRule ^(.+\.php)s$ $1 [T=application/x-httpd-php-source]
  • valveSkip|VS”(跳过阀)
    此标志可用于设置阀的条件执行。当此标志被设置且规则匹配时,重写阀将跳过Catalina管道中的下一个阀。如果重写阀是管道中的最后一个,则该标志将被忽略,并且将调用容器的基本阀。如果已发生重写,则该标志将不起任何作用。