<-
Apache > HTTP 服务器 > 文档 > 版本 2.4 > 重写

使用 mod_rewrite 进行重定向和重写

可用语言:  en  |  fr 

本文档是对 mod_rewrite 参考文档 的补充。它描述了如何使用 mod_rewrite 来重定向和重写请求。这包括许多 mod_rewrite 常见用法的示例,以及对每个示例如何工作的详细描述。

请注意,这些示例中的许多在您的特定服务器配置中无法直接使用,因此您需要理解它们,而不仅仅是将示例复制粘贴到您的配置中。
Support Apache!

另请参阅

top

从旧到新(内部)

描述

假设我们最近将页面 foo.html 重命名为 bar.html,现在想要提供旧的 URL 以实现向后兼容。但是,我们希望使用旧 URL 的用户甚至不会注意到页面已被重命名 - 也就是说,我们不希望他们在浏览器中看到地址发生变化。

解决方案

我们使用以下规则将旧 URL 在内部重写为新的 URL

RewriteEngine  on
RewriteRule    "^/foo\.html$"  "/bar.html" [PT]
top

从旧到新(外部)

描述

再次假设我们最近将页面 foo.html 重命名为 bar.html,现在想要提供旧的 URL 以实现向后兼容。但这次我们希望使用旧 URL 的用户能够看到新的 URL,即他们的浏览器地址栏也应该发生变化。

解决方案

我们强制执行 HTTP 重定向到新的 URL,这会导致浏览器和用户视图发生变化

RewriteEngine  on
RewriteRule    "^/foo\.html$"  "bar.html"  [R]
讨论

在本例中,与上面的 内部 示例相比,我们可以简单地使用 Redirect 指令。在前面的示例中使用 mod_rewrite 的目的是向客户端隐藏重定向

Redirect "/foo.html" "/bar.html"
top

资源已移至另一台服务器

描述

如果资源已移至另一台服务器,您可能希望在一段时间内让旧服务器上的 URL 继续工作,以便人们更新他们的书签。

解决方案

您可以使用 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/"
top

从静态到动态

描述

我们如何以无缝的方式将静态页面 foo.html 转换为动态变体 foo.cgi,即不引起浏览器/用户的注意。

解决方案

我们只需将 URL 重写为 CGI 脚本,并强制处理程序为 cgi-script,以便它作为 CGI 程序执行。这样,对 /~quux/foo.html 的请求在内部会导致调用 /~quux/foo.cgi

RewriteEngine  on
RewriteBase    "/~quux/"
RewriteRule    "^foo\.html$"  "foo.cgi"  [H=cgi-script]
top

文件扩展名更改的向后兼容性

描述

我们如何在将 document.YYYY 迁移到 document.XXXX 后使 URL 向后兼容(仍然虚拟存在),例如在将一堆 .html 文件转换为 .php 后。

解决方案

我们将名称重写为其基本名称,并测试新扩展名是否存在。如果存在,我们将使用该名称,否则我们将 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>
讨论

本示例利用了 mod_rewrite 的一个经常被忽视的功能,它利用了规则集的执行顺序。具体来说,mod_rewrite 在评估 RewriteCond 指令之前会先评估 RewriteRule 的左侧。因此,在评估 RewriteCond 指令时,$1 已经定义。这使我们能够使用相同的基本文件名来测试原始文件 (document.html) 和目标文件 (document.php) 是否存在。

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

top

规范主机名

描述
此规则的目标是强制使用特定主机名,而不是其他可能用于访问同一站点的其他主机名。例如,如果您希望强制使用 www.example.com 而不是 example.com,您可以使用以下方法的变体。
解决方案

解决此问题的最佳方法根本不涉及 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 重定向到 www.example.com,对于 example.com 的所有可能值,您可以使用以下方法

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

这些规则集可以在您的主服务器配置文件中使用,也可以在放置在服务器 DocumentRoot 中的 .htaccess 文件中使用。

top

在多个目录中搜索页面

描述

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

解决方案

以下规则集在两个目录中搜索资源,如果在任何一个位置都找不到,它将尝试直接从请求的位置提供服务。

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]
top

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

描述

我们有许多网站镜像,并且希望将用户重定向到位于他们所在国家/地区的镜像。

解决方案

查看请求客户端的主机名,我们可以确定他们来自哪个国家/地区。如果我们无法对他们的 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 -- 多路复用映射

de http://www.example.de/
uk http://www.example.uk/
com http://www.example.com/
##EOF##

讨论
此规则集依赖于 HostNameLookups 设置为 on,这可能会导致性能大幅下降。

RewriteCond 指令捕获请求客户端主机名的最后一部分 - 国家代码 - 然后,以下 RewriteRule 使用该值在映射文件中查找相应的镜像主机。

top

浏览器相关内容

描述

我们希望根据请求内容的浏览器或用户代理提供不同的内容。

解决方案

我们必须根据 HTTP 标头“User-Agent”来决定要提供哪个内容。以下配置执行以下操作:如果 HTTP 标头“User-Agent”包含“Mozilla/3”,则页面 foo.html 将重写为 foo.NS.html,并且重写停止。如果浏览器是“Lynx”或版本 1 或 2 的“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]
top

规范 URL

描述

在某些 Web 服务器上,资源可能有多个 URL。通常,存在规范 URL(实际上使用和分发)以及仅仅是快捷方式、内部 URL 等等。无论用户使用哪个 URL 发出请求,他们最终都应该在浏览器地址栏中看到规范 URL。

解决方案

我们对所有非规范 URL 执行外部 HTTP 重定向,以在浏览器的地址栏中修复它们,并用于所有后续请求。在下面的示例规则集中,我们将 /puppies/canines 替换为规范的 /dogs

RewriteRule   "^/(puppies|canines)/(.*)"    "/dogs/$2"  [R]
讨论
这实际上应该使用 Redirect 或 RedirectMatch 指令来完成
RedirectMatch "^/(puppies|canines)/(.*)" "/dogs/$2"
top

已移动 DocumentRoot

描述

通常,Web 服务器的 DocumentRoot 直接与 URL“/”相关。但通常这些数据并不真正处于顶级优先级。例如,您可能希望访问者在首次进入网站时转到特定子目录 /about/。这可以使用以下规则集来完成

解决方案

我们将 URL / 重定向到 /about/

RewriteEngine on
RewriteRule   "^/$"  "/about/"  [R]

请注意,这也可以使用 RedirectMatch 指令来处理

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

另请注意,该示例仅重写根 URL。也就是说,它重写了对 http://example.com/ 的请求,但没有重写对 http://example.com/page.html 的请求。如果您实际上更改了 DocumentRoot - 也就是说,如果您所有的内容实际上都在该子目录中,那么最好简单地更改您的 DocumentRoot 指令,或者将所有内容向上移动一个目录,而不是重写 URL。

top

后备资源

描述
您希望单个资源(例如,某个文件,如 index.php)处理所有进入特定目录的请求,除了那些应该转到现有资源(例如,图像或 css 文件)的请求。
解决方案

从版本 2.2.16 开始,您应该为此使用 FallbackResource 指令

<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 文件中使用,也可以在 <Directory> 块中使用。

top

重写查询字符串

描述
您希望从查询字符串中捕获特定值,并将其替换或合并到 URL 的另一个组件中。
解决方案

本节中的许多解决方案都使用相同的条件,该条件将匹配的值保留在 %2 后向引用中。%1 是查询字符串的开头(直到感兴趣的键),%3 是剩余部分。此条件对于灵活性以及避免替换中的双 '&&' 来说有点复杂。

  • 此解决方案删除匹配的键和值。
    # 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]
  • 此解决方案显示了前面几个解决方案的相反情况,将 URL 中的路径组件(可能是 PATH_INFO)复制到查询字符串中。
    # The desired URL might be /products/kitchen-sink, and the script expects
    # /path?products=kitchen-sink.
    RewriteRule "^/?path/([^/]+)/([^/]+)" "/path?$1=$2" [PT]

可用语言:  en  |  fr 

top

评论

注意
这不是问答区。此处放置的评论应指向有关改进文档或服务器的建议,如果这些评论已被实施或被认为无效/偏离主题,则可能会被我们的版主删除。有关如何管理 Apache HTTP Server 的问题应发送到我们的 IRC 频道 #httpd(在 Libera.chat 上),或发送到我们的 邮件列表