Apache HTTP 服务器版本 2.5

本文档是 mod_rewrite
参考文档的补充。它描述了如何使用
mod_rewrite 来重定向和重映射请求。
其中包括许多 mod_rewrite 的常见用法示例,
以及每个示例工作原理的详细描述。
从旧到新(内部)
从旧到新的重写(外部)
资源已移至另一台服务器
从静态到动态
文件扩展名更改的向后兼容性
规范主机名
在多个目录中搜索页面
重定向到地理分布式服务器
规范 URL
移动的 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(例如将一批 .html
文件翻译为 .php)之后,
如何使 URL 保持向后兼容(仍然虚拟存在)?
仅当具有新扩展名的目标文件存在而具有旧扩展名的原始文件不存在时, 才将 URL 从旧扩展名重写为新扩展名。否则,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>
指令来完成(2.4 及更高版本):
<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——
例如,你需要它与一组更大的 RewriteRule 一起使用——
你可以使用以下方案之一。
对于运行在非 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
使用该值在映射文件中查找适当的镜像主机。
在某些 Web 服务器上,一个资源可能有多个 URL。 通常有规范 URL(即实际使用和分发的 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 指令,
或将所有内容向上移动一个目录,而不是重写 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]