apache / 2.4 / reference / rewrite-remapping.html

使用 mod_rewrite 重定向和重新 Map

本文档是对mod_rewrite reference documentation的补充。它描述了如何使用mod_rewrite重定向和重新 Map 请求。这包括 mod_rewrite 常用用法的许多示例,包括每种用法的详细说明。

Warning

请注意,这些示例中的许多示例在您的特定服务器配置中都不会起作用,因此,理解它们,而不只是将示例剪切并粘贴到您的配置中,这一点很重要。

从旧到新(内部)

  • Description:

    • 假设我们最近将页面foo.html重命名为bar.html,现在想提供旧的 URL 以实现向后兼容。但是,我们希望旧 URL 的用户甚至不能识别页面已被重命名-也就是说,我们不希望在其浏览器中更改地址。
  • Solution:

    • 我们通过以下规则在内部将旧网址重写为新网址:
RewriteEngine  on
RewriteRule    "^/foo\.html$"  "/bar.html" [PT]

从旧到新的重写(外部)

  • Description:

    • 再次假设我们最近将页面foo.html重命名为bar.html,现在希望提供旧的 URL 以实现向后兼容。但是这次,我们希望旧网址的用户得到新网址的提示,即他们的浏览器的“位置”字段也应更改。
  • Solution:

    • 我们强制将 HTTP 重定向到新 URL,从而导致浏览器发生更改,因此用户可以查看:
RewriteEngine  on
RewriteRule    "^/foo\.html$"  "bar.html"  [R]
  • Discussion

    • 在此示例中,与上面的internal示例相比,我们可以简单地使用 Redirect 指令。在较早的示例中使用了 mod_rewrite,以便从 Client 端隐藏重定向:
Redirect "/foo.html" "/bar.html"

资源已移至另一台服务器

  • Description:

    • 如果资源已移至另一台服务器,您可能希望在人们更新其书签的同时,URL 在旧服务器上 continue 工作一段时间。
  • Solution:

    • 您可以使用mod_rewrite将这些 URL 重定向到新服务器,但也可以考虑使用 Redirect 或 RedirectMatch 指令。
#With mod_rewrite
RewriteEngine on
RewriteRule   "^/docs/(.+)"  "http://new.example.com/docs/$1"  [R,L]
#With RedirectMatch
RedirectMatch "^/docs/(.*)" "http://new.example.com/docs/$1"
#With Redirect
Redirect "/docs/" "http://new.example.com/docs/"

从静态到动态

  • Description:

    • 我们如何无缝地将静态页面foo.html转换为动态变体foo.cgi,即在没有浏览器/用户通知的情况下。
  • Solution:

    • 我们只是将 URL 重写为 CGI 脚本,并强制处理程序为 cgi-script ,以便将其作为 CGI 程序执行。这样,对/~quux/foo.html的请求会在内部导致/~quux/foo.cgi的调用。
RewriteEngine  on
RewriteBase    "/~quux/"
RewriteRule    "^foo\.html$"  "foo.cgi"  [H=cgi-script]

向后兼容的文件 extensions 更改

  • Description:

    • 在将document.YYYY迁移到document.XXXX之后,如何使网址向后兼容(实际上仍然存在),例如将一堆.html文件转换为.php之后?
  • Solution:

    • 我们将该名称 Rewrite 为其基本名称,并测试是否存在新 extensions。如果存在,则使用该名称,否则,将 URL 重写为其原始状态。
#   backward compatibility ruleset for
#   rewriting document.html to document.php
#   when and only when document.php exists
<Directory "/var/www/htdocs">
    RewriteEngine on
    RewriteBase "/var/www/htdocs"

    RewriteCond "$1.php" -f
    RewriteCond "$1.html" !-f
    RewriteRule "^(.*).html$" "$1.php"
</Directory>
  • Discussion

    • 本示例通过利用规则集的执行 Sequences,使用了 mod_rewrite 经常被忽略的功能。特别是,mod_rewrite 在评估 RewriteCond 指令之前先评估 RewriteRule 的左侧。因此,在评估 RewriteCond 指令时已经定义了$ 1.这使我们可以使用相同的基本文件名测试原始(document.html)和目标(document.php)文件的存在。

此规则集旨在用于每个目录的上下文(在块中或.htaccess 文件中),以便-f检查正在查看正确的目录路径。您可能需要设置RewriteBase指令来指定您正在使用的目录库。

Canonical Hostnames

  • Description:

    • 该规则的目标是强制使用特定主机名,而不是可能用于访问同一站点的其他主机名。例如,如果您希望强制使用 www.example.com 而不是 example.com ,则可以使用以下配方的变体。
  • Solution:

    • 解决此问题的最佳方法完全不涉及 mod_rewrite,而是将虚拟主机中放置的Redirect指令用于非规范主机名。
<VirtualHost *:80>
  ServerName undesired.example.com
  ServerAlias example.com notthis.example.com

  Redirect "/" "http://www.example.com/"
</VirtualHost>

<VirtualHost *:80>
  ServerName www.example.com
</VirtualHost>

您也可以使用<If>指令完成此操作:

<If "%{HTTP_HOST} != 'www.example.com'">
    Redirect "/" "http://www.example.com/"
</If>

或者,例如,要将站点的一部分重定向到 HTTPS,可以执行以下操作:

<If "%{SERVER_PROTOCOL} != 'HTTPS'">
    Redirect "/admin/" "https://www.example.com/admin/"
</If>

如果出于某种原因,您仍然想使用mod_rewrite-例如,如果您需要使用它来使用更大的 RewriteRules 集,则可以使用下面的一种配方。

对于在 80 以外的端口上运行的站点:

RewriteCond "%{HTTP_HOST}"   "!^www\.example\.com" [NC]
RewriteCond "%{HTTP_HOST}"   "!^$"
RewriteCond "%{SERVER_PORT}" "!^80$"
RewriteRule "^/?(.*)"        "http://www.example.com:%{SERVER_PORT}/$1" [L,R,NE]

对于在端口 80 上运行的站点

RewriteCond "%{HTTP_HOST}"   "!^www\.example\.com" [NC]
RewriteCond "%{HTTP_HOST}"   "!^$"
RewriteRule "^/?(.*)"        "http://www.example.com/$1" [L,R,NE]

如果您想对所有域名通用地执行此操作-也就是说,如果要将 example.com 的所有可能值都重定向 example.com 到 www.example.com **,您可以使用以下配方:

RewriteCond "%{HTTP_HOST}" "!^www\." [NC]
RewriteCond "%{HTTP_HOST}" "!^$"
RewriteRule "^/?(.*)"      "http://www.%{HTTP_HOST}/$1" [L,R,NE]

这些规则集将在您的主服务器配置文件中或在服务器的DocumentRoot中放置的.htaccess文件中工作。

搜索多个目录中的页面

  • Description:

    • 特定资源可能存在于多个位置之一,我们希望在请求资源时在那些位置中查找该资源。也许我们最近重新排列了目录结构,将内容分为几个位置。
  • Solution:

    • 以下规则集在两个目录中搜索以找到资源,并且,如果在两个位置均未找到资源,则将尝试仅在请求的位置之外提供资源。
RewriteEngine on

#   first try to find it in dir1/...
#   ...and if found stop and be happy:
RewriteCond         "%{DOCUMENT_ROOT}/dir1/%{REQUEST_URI}"  -f
RewriteRule "^(.+)" "%{DOCUMENT_ROOT}/dir1/$1"  [L]

#   second try to find it in dir2/...
#   ...and if found stop and be happy:
RewriteCond         "%{DOCUMENT_ROOT}/dir2/%{REQUEST_URI}"  -f
RewriteRule "^(.+)" "%{DOCUMENT_ROOT}/dir2/$1"  [L]

#   else go on for other Alias or ScriptAlias directives,
#   etc.
RewriteRule   "^"  "-"  [PT]

重定向到地理位置分布式服务器

  • Description:

    • 我们的网站有很多镜像,并希望将人们重定向到他们所所在的国家/locale。
  • Solution:

    • 查看发出请求的 Client 的主机名,我们确定他们来自哪个国家。如果我们无法在其 IP 地址上进行查找,则会退回到默认服务器。

我们将使用RewriteMap指令来构建我们希望使用的服务器列表。

HostnameLookups on
RewriteEngine on
RewriteMap    multiplex         "txt:/path/to/map.mirrors"
RewriteCond   "%{REMOTE_HOST}"  "([a-z]+)$" [NC]
RewriteRule   "^/(.*)$"  "${multiplex:%1|http://www.example.com/}$1"  [R,L]

## map.mirrors -- Multiplexing Map de http://www.example.de/ uk http://www.example.uk/ com http://www.example.com/ ##EOF##

  • Discussion

    Warning

此规则集依赖于HostNameLookups被设置为on,这可能会严重影响性能。

RewriteCond指令捕获请求 Client 端的主机名的最后一部分-国家/locale 代码-随后的 RewriteRule 使用该值在 Map 文件中查找适当的镜像主机。

浏览器相关内容

  • Description:

    • 我们希望基于请求内容的浏览器或用户代理提供不同的内容。
  • Solution:

    • 我们必须根据 HTTP Headers“ User-Agent”确定要提供的内容。以下配置执行以下操作:如果 HTTP Headers“ User-Agent”包含“ Mozilla/3”,则页面foo.html将被重写为foo.NS.html,并且重写将停止。如果浏览器是版本 1 或 2 的“ Lynx”或“ Mozilla”,则 URL 变为foo.20.html。所有其他浏览器都收到foo.32.html页。这是通过以下规则集完成的:
RewriteCond "%{HTTP_USER_AGENT}"  "^Mozilla/3.*"
RewriteRule "^foo\.html$"         "foo.NS.html"          [L]

RewriteCond "%{HTTP_USER_AGENT}"  "^Lynx/" [OR]
RewriteCond "%{HTTP_USER_AGENT}"  "^Mozilla/[12]"
RewriteRule "^foo\.html$"         "foo.20.html"          [L]

RewriteRule "^foo\.html$"         "foo.32.html"          [L]

Canonical URLs

  • Description:

    • 在某些 Web 服务器上,一个资源有多个 URL。通常,存在规范的 URL(实际使用和分发的 URL)以及只是快捷方式,内部的 URL 等。不管用户随请求提供了哪个 URL,他们最终都应该在浏览器地址栏中看到规范的 URL。
  • Solution:

    • 我们对所有非规范 URL 进行外部 HTTP 重定向,以在浏览器的位置视图以及所有后续请求中修复它们。在下面的示例规则集中,我们用标准/dogs替换了/puppies/canines
RewriteRule   "^/(puppies|canines)/(.*)"    "/dogs/$2"  [R]
  • Discussion:

这实际上应该使用 Redirect 或 RedirectMatch 指令完成:

RedirectMatch "^/(puppies|canines)/(.*)" "/dogs/$2"

Moved DocumentRoot

  • Description:

    • 通常,Web Service 器的DocumentRoot直接与 URL“ /”相关。但是通常这些数据并不是 true 的最高优先级。例如,您可能希望访问者在首次进入站点时进入特定的子目录/about/。这可以使用以下规则集来完成:
  • Solution:

    • 我们将 URL /重定向到/about/
RewriteEngine on
RewriteRule   "^/$"  "/about/"  [R]

请注意,这也可以使用RedirectMatch指令进行处理:

RedirectMatch "^/$" "http://example.com/about/"

另请注意,该示例仅重写根 URL。也就是说,它将重写对http://example.com/的请求,而不是对http://example.com/page.html的请求。如果您实际上已更改了文档根目录-也就是说,如果所有内容实际上都在该子目录中,则最好更改DocumentRoot指令,或将所有内容上移到一个目录,而不是重写网址。

Fallback Resource

  • Description:

    • 您希望一个资源(例如,某个文件,例如 index.php)来处理到达特定目录的所有请求,但那些应该进入现有资源(例如图像或 css 文件)的请求除外。
  • Solution:

<Directory "/var/www/my_blog">
  FallbackResource "index.php"
</Directory>

但是,在 Apache 的早期版本中,或者如果您的需求比这更复杂,则可以使用以下重写集的变体来完成相同的操作:

<Directory "/var/www/my_blog">
  RewriteBase "/my_blog"

  RewriteCond "/var/www/my_blog/%{REQUEST_FILENAME}" !-f
  RewriteCond "/var/www/my_blog/%{REQUEST_FILENAME}" !-d
  RewriteRule "^" "index.php" [PT]
</Directory>

另一方面,如果您希望将请求的 URI 作为查询字符串参数传递给 index.php,则可以将该 RewriteRule 替换为:

RewriteRule "(.*)" "index.php?$1" [PT,QSA]

请注意,这些规则集可以在.htaccess文件以及块中使用。

重写查询字符串

  • Description:

    • 您想从查询字符串中捕获特定值,然后替换它或将其合并到 URL 的另一个组件中。
  • Solutions:

    • 本节中的许多解决方案都将使用相同的条件,这会将匹配的值保留在%2 反向引用中。 %1 是查询字符串的开头(取决于兴趣关键字),而%3 是其余部分。对于灵 Active 和避免替换中使用双'&&'而言,此条件有点复杂。

此解决方案删除匹配的键和值:

# Remove mykey=???
RewriteCond "%{QUERY_STRING}" "(.*(?:^|&))mykey=([^&]*)&?(.*)&?$"
RewriteRule "(.*)" "$1?%1%3"

此解决方案在 URL 替换中使用捕获的值,并通过附加“?”来丢弃原始查询的其余部分:

# Copy from query string to PATH_INFO
RewriteCond "%{QUERY_STRING}" "(.*(?:^|&))mykey=([^&]*)&?(.*)&?$"
RewriteRule "(.*)" "$1/products/%2/?" [PT]

此解决方案在随后的条件下检查捕获的值:

# Capture the value of mykey in the query string
RewriteCond "%{QUERY_STRING}" "(.*(?:^|&))mykey=([^&]*)&?(.*)&?$"
RewriteCond "%2" !=not-so-secret-value 
RewriteRule "(.*)" - [F]

此解决方案与前面的解决方案相反,将路径部分(可能是 PATH_INFO)从 URL 复制到查询字符串中。

# The desired URL might be /products/kitchen-sink, and the script expects
# /path?products=kitchen-sink.
RewriteRule "^/?path/([^/]+)/([^/]+)" "/path?$1=$2" [PT]