Apache HTTP Server 版本 2.4
本文档是对 mod_rewrite
参考文档 的补充。它描述了如何使用 mod_rewrite
来重定向和重写请求。这包括许多 mod_rewrite 常见用法的示例,以及对每个示例如何工作的详细描述。
DocumentRoot
假设我们最近将页面 foo.html
重命名为 bar.html
,现在想要提供旧的 URL 以实现向后兼容。但是,我们希望使用旧 URL 的用户甚至不会注意到页面已被重命名 - 也就是说,我们不希望他们在浏览器中看到地址发生变化。
我们使用以下规则将旧 URL 在内部重写为新的 URL
RewriteEngine on RewriteRule "^/foo\.html$" "/bar.html" [PT]
再次假设我们最近将页面 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"
如果资源已移至另一台服务器,您可能希望在一段时间内让旧服务器上的 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/"
我们如何以无缝的方式将静态页面 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]
我们如何在将 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
指令来指定您正在使用的目录基础。
解决此问题的最佳方法根本不涉及 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
文件中使用。
特定资源可能存在于多个位置之一,我们希望在请求时在这些位置查找资源。也许我们最近重新排列了目录结构,将内容划分到多个位置。
以下规则集在两个目录中搜索资源,如果在任何一个位置都找不到,它将尝试直接从请求的位置提供服务。
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]
我们有许多网站镜像,并且希望将用户重定向到位于他们所在国家/地区的镜像。
查看请求客户端的主机名,我们可以确定他们来自哪个国家/地区。如果我们无法对他们的 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 使用该值在映射文件中查找相应的镜像主机。
我们希望根据请求内容的浏览器或用户代理提供不同的内容。
我们必须根据 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]
在某些 Web 服务器上,资源可能有多个 URL。通常,存在规范 URL(实际上使用和分发)以及仅仅是快捷方式、内部 URL 等等。无论用户使用哪个 URL 发出请求,他们最终都应该在浏览器地址栏中看到规范 URL。
我们对所有非规范 URL 执行外部 HTTP 重定向,以在浏览器的地址栏中修复它们,并用于所有后续请求。在下面的示例规则集中,我们将 /puppies
和 /canines
替换为规范的 /dogs
。
RewriteRule "^/(puppies|canines)/(.*)" "/dogs/$2" [R]
RedirectMatch "^/(puppies|canines)/(.*)" "/dogs/$2"
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。
从版本 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> 块中使用。
本节中的许多解决方案都使用相同的条件,该条件将匹配的值保留在 %2 后向引用中。%1 是查询字符串的开头(直到感兴趣的键),%3 是剩余部分。此条件对于灵活性以及避免替换中的双 '&&' 来说有点复杂。
# Remove mykey=??? RewriteCond "%{QUERY_STRING}" "(.*(?:^|&))mykey=([^&]*)&?(.*)&?$" RewriteRule "(.*)" "$1?%1%3"
# 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]
# The desired URL might be /products/kitchen-sink, and the script expects # /path?products=kitchen-sink. RewriteRule "^/?path/([^/]+)/([^/]+)" "/path?$1=$2" [PT]