Apache Rewrite 重定向问题汇总
Apache RewriteRule 常用标志及实例
- R (force redirect) 强制外部重定向。默认通常为
302,也可以指定状态码,例如[R=301]。 - F (force URL to be forbidden) 禁用 URL,返回
403 Forbidden。 - G (force URL to be gone) 强制 URL 为 Gone,返回
410 Gone。 - P (force proxy) 强制使用代理转发,需要启用
mod_proxy,HTTP 后端通常还需要mod_proxy_http。 - L (last rule) 表示当前规则是本轮规则处理中的最后一条,停止继续分析后面的 RewriteRule。需要注意,内部重写后在某些上下文中仍可能重新进入 Rewrite 流程。
- END 结束当前请求的 Rewrite 处理。Apache 2.4+ 支持,在
.htaccess或目录级 Rewrite 中比L更彻底。 - N (next round) 使用当前已经重写后的 URL,重新从第一条 RewriteRule 开始运行重写过程。使用不当容易产生死循环。
- C (chained with next rule) 与下一条规则关联。如果当前规则匹配,则继续正常处理;如果当前规则不匹配,则跳过后面所有属于当前 Chain 的规则。
- T=MIME-type (force MIME type) 强制指定 MIME 类型。
- NS (used only if no internal sub-request) 只在当前请求不是 Apache 内部子请求时执行该规则。
- NC (no case) 正则匹配不区分大小写。
- QSA (query string append) 将原始 Query String 追加到新的 Query String 后面。
- QSD (query string discard) 丢弃原始 Query String,Apache 2.4+ 支持。
- NE (no URI escaping of output) 外部重定向时,不对 Rewrite 生成的结果执行默认的特殊字符 URI 转义。
- PT (pass through to next handler) Rewrite 后将新的 URI 重新交给 Apache URL Mapping 阶段处理,常用于配合
Alias、ScriptAlias等。 - S=num (skip next rule(s)) 当前规则匹配后跳过后面的
num条 RewriteRule。 - E=VAR:VAL (set environment variable) 设置环境变量。
- B (escape backreferences) 对
$1、$2、%1等反向引用中的特殊字符进行转义。
实用的实例
简单的虚拟主机配置:
<VirtualHost *:80>
DocumentRoot "E:/JAVA_WorkSpace/bsei_mapenjoy_passport_tomcat/WebRoot"
ServerName www.boshilian.com
ErrorDocument 404 /index.html
<IfModule mod_rewrite.c>
RewriteEngine On
# 内部 Rewrite,浏览器 URL 不变
RewriteRule ^/22\.htm(.+)$ /help.jsp?$1 [L]
# 外部 302 Redirect,浏览器 URL 改变
RewriteRule ^/33\.htm(.+)$ /help.jsp?$1 [R=302,L]
# 外部跨域 Redirect
RewriteRule ^/44\.htm(.+)$ https://www.sohu.com/?$1 [R=302,L]
# 外部永久 Redirect
RewriteRule ^/55\.htm(.+)$ https://www.sohu.com/?$1 [R=301,L]
# Apache 内部反向代理,浏览器 URL 不变
RewriteRule ^/66\.htm(.+)$ https://www.sohu.com/?$1 [P,L]
# 根据 Query String 进行代理
RewriteCond %{QUERY_STRING} ^t/=(.+)$ [NC]
RewriteRule ^/api$ https://www.sina.com.cn/?t=%1 [P,L]
</IfModule>
</VirtualHost>
上面的规则位于
<VirtualHost>/ Server Context 中,因此 Pattern 可以匹配以/开头的 URL Path。 如果规则放在.htaccess中,通常需要去掉 Pattern 开头的/。
最简单的重定向
下面列一下常用的 Rewrite 和 Redirect。
URL 不发生变化,内部 Rewrite,类似 Java 中的 forward
RewriteRule ^/22\.htm(.+)$ /help.jsp?$1 [L]
例如访问:
/22.htmabc
Apache 内部处理为:
/help.jsp?abc
但是浏览器地址仍然保持:
/22.htmabc
因此:
Rewrite + L
属于服务器内部重写,客户端通常不知道目标资源发生了变化。
URL 发生变化,使用 Redirect 转向
RewriteRule ^/33\.htm(.+)$ /help.jsp?$1 [R=302,L]
例如访问:
/33.htmabc
Apache 返回类似:
HTTP/1.1 302 Found
Location: /help.jsp?abc
浏览器随后请求:
/help.jsp?abc
浏览器地址栏会发生变化。
如果需要永久重定向:
RewriteRule ^/33\.htm(.+)$ /help.jsp?$1 [R=301,L]
其中:
301 = 永久重定向
302 = 临时重定向
307 = 临时重定向,并保留 HTTP Method
308 = 永久重定向,并保留 HTTP Method
跨域 Redirect
例如:
RewriteRule ^/44\.htm(.+)$ https://www.sohu.com/?$1 [R=302,L]
访问:
/44.htmabc
浏览器会被重定向到:
https://www.sohu.com/?abc
地址栏会发生变化。
需要特别注意:
下面这种写法:
RewriteRule ^/44\.htm(.+)$ https://www.sohu.com/?$1 [L]
虽然没有写:
R
但是目标是另外一个 Host 的绝对 URL:
https://www.sohu.com/
Apache 仍会把它作为外部 Redirect 处理。
因此不建议使用这种容易产生歧义的写法:
RewriteRule ^/44\.htm(.+)$ https://www.sohu.com/?$1 [L]
建议明确写为:
RewriteRule ^/44\.htm(.+)$ https://www.sohu.com/?$1 [R=302,L]
这样更容易理解和维护。
永久跨域 Redirect
RewriteRule ^/55\.htm(.+)$ https://www.sohu.com/?$1 [R=301,L]
访问:
/55.htmabc
返回:
301
并跳转到:
https://www.sohu.com/?abc
适用于:
- 网站域名迁移
- 老 URL 永久迁移
- SEO URL 规范化
使用 Proxy 转发
RewriteRule ^/66\.htm(.+)$ https://www.sohu.com/?$1 [P,L]
这里和 [R] 有本质区别。
访问:
http://www.boshilian.com/66.htmabc
浏览器依然访问:
http://www.boshilian.com/66.htmabc
但是 Apache 内部会去请求:
https://www.sohu.com/?abc
流程类似:
浏览器
|
| GET /66.htmabc
v
Apache
|
| 内部 Proxy
v
www.sohu.com
然后 Apache 把远程服务器的响应返回给浏览器。
因此:
R = 浏览器自己去请求目标服务器
P = Apache 帮浏览器请求目标服务器
使用 [P] 通常需要启用:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
如果代理 HTTPS 后端,还要确保 Apache 的相关 SSL/Proxy 配置已经启用。
RewriteCond 配合 Query String
例如:
RewriteCond %{QUERY_STRING} ^t/=(.+)$ [NC]
RewriteRule ^/api$ https://www.sina.com.cn/?t=%1 [P,L]
假设请求:
/api?t/=abc
其中:
%{QUERY_STRING}
的值为:
t/=abc
RewriteCond:
^t/=(.+)$
匹配成功。
捕获内容:
abc
这里:
%1 = abc
于是 RewriteRule 的代理目标为:
https://www.sina.com.cn/?t=abc
$1 和 %1 的区别
这是 RewriteRule 中比较容易混淆的地方。
$1
来自 RewriteRule 的正则捕获组。
例如:
RewriteRule ^/user/([0-9]+)$ /profile.jsp?id=$1 [L]
访问:
/user/123
则:
$1 = 123
最后内部重写:
/profile.jsp?id=123
%1
来自 RewriteCond 的正则捕获组。
例如:
RewriteCond %{QUERY_STRING} ^id=([0-9]+)$
RewriteRule ^/user$ /profile.jsp?id=%1 [L]
访问:
/user?id=123
则:
%1 = 123
可以简单记忆:
RewriteRule 捕获组 → $1、$2、$3
RewriteCond 捕获组 → %1、%2、%3
Query String 追加
例如:
RewriteRule ^/22\.htm(.+)$ /help.jsp?$1 [QSA,L]
访问:
/22.htmabc?from=test
因为使用了:
QSA
新的 Query String 会和旧 Query String 合并:
/help.jsp?abc&from=test
如果新的 Substitution 已经自己生成 Query String,而又需要保留原请求参数,应该明确使用:
QSA
删除 Query String
Apache 2.4 可以使用:
QSD
例如:
RewriteRule ^/clean$ /index.html [QSD,L]
访问:
/clean?a=1&b=2
最终处理:
/index.html
原来的:
?a=1&b=2
会被删除。
NE
NE 的作用主要是:
外部 Redirect 时,不对 Rewrite 生成的结果中的特殊字符执行默认 URI escaping。
例如:
RewriteRule ^/anchor/(.+)$ /page#$1 [R=302,NE,L]
访问:
/anchor/test
希望 Redirect 到:
/page#test
NE 可以防止 Rewrite 对 # 等字符执行默认的转义处理。
因此 NE 更准确的理解不是:
不转义输入 URL
而是:
不对 Rewrite 产生的 Redirect 输出执行默认 URI 转义
PT
例如:
Alias /files/ /data/downloads/
RewriteRule ^/download/(.*)$ /files/$1 [PT,L]
访问:
/download/test.zip
首先 Rewrite:
/download/test.zip
↓
/files/test.zip
因为使用:
PT
Apache 会把新的:
/files/test.zip
重新交给 URL Mapping 阶段。
于是:
Alias /files/ /data/downloads/
继续生效。
最终对应:
/data/downloads/test.zip
所以:
PT
并不是:
重新跳到另外一条 RewriteRule
而更准确地说:
Rewrite 后重新交给 Alias、ScriptAlias 等 URI Mapping 模块处理
N
N 表示:
Next Round
使用当前已经重写后的 URL,从整个 RewriteRule 规则集第一条重新开始执行。
例如:
RewriteRule ^/(.*)//(.*)$ /$1/$2 [N]
访问:
/a////b
可能依次处理为:
/a///b
/a//b
/a/b
需要特别注意:
N
很容易造成 Rewrite 死循环,因此生产环境中应谨慎使用。
C
C 表示:
Chain
例如:
RewriteRule ^/foo/(.*)$ /bar/$1 [C]
RewriteRule ^/bar/(.*)$ /test/$1 [C]
RewriteRule ^/test/(.*)$ /final/$1 [L]
如果第一条规则不匹配:
后面属于当前 Chain 的规则全部跳过
如果第一条匹配,则继续正常处理后面的 Chain。
实际复杂配置中,一般使用:
RewriteCond
往往会比大量使用 [C] 更容易阅读和维护。
L 与 END
L:
RewriteRule ^ /index.jsp [L]
表示:
停止当前这一轮 RewriteRule 处理
但内部 Rewrite 后,在某些情况下 Apache 可能再次进入 Rewrite。
Apache 2.4+ 可以使用:
RewriteRule ^ /index.jsp [END]
END 比 L 更彻底,尤其适用于:
.htaccess
<Directory>
等 per-directory Rewrite 场景。
简单理解:
L
=
这一轮规则到这里结束
END
=
当前请求的 per-directory Rewrite 到这里结束
Rewrite、Redirect 和 Proxy 的区别
最重要的是区分下面三种行为。
Rewrite
RewriteRule ^/a$ /b [L]
流程:
浏览器
|
| /a
v
Apache
|
| 内部 Rewrite
v
/b
浏览器地址:
/a
不发生变化。
Redirect
RewriteRule ^/a$ /b [R=302,L]
流程:
浏览器
|
| /a
v
Apache
|
| 302 Location: /b
v
浏览器
|
| 再请求 /b
v
Apache
浏览器地址变成:
/b
Proxy
RewriteRule ^/a$ https://backend.example.com/b [P,L]
流程:
浏览器
|
| /a
v
Apache
|
| Proxy
v
backend.example.com
|
| Response
v
Apache
|
v
浏览器
浏览器地址仍然:
/a
总结
可以简单记忆:
[L]
服务器内部 Rewrite
浏览器 URL 通常不变
[R]
外部 Redirect
浏览器 URL 改变
[P]
服务器内部 Proxy
浏览器 URL 不变
[F]
返回 403
[G]
返回 410
[NC]
忽略大小写
[QSA]
追加 Query String
[QSD]
删除 Query String
[PT]
Rewrite 后重新交给 URI Mapping
[N]
从第一条 RewriteRule 重新执行
[S=n]
跳过后面 n 条 RewriteRule
[E=VAR:VAL]
设置环境变量
[END]
彻底结束当前 per-directory Rewrite
尤其需要注意:
RewriteRule ^/test$ https://other.example.com/ [L]
目标是其他 Host 的绝对 URL 时,即使没有显式写 [R],Apache 仍可能将其作为外部 Redirect。
因此跨域 Redirect 推荐明确写:
RewriteRule ^/test$ https://other.example.com/ [R=302,L]
如果真正需要 Apache 内部去请求另一个服务器,则应该使用:
RewriteRule ^/test$ https://other.example.com/ [P,L]
核心区别:
Rewrite = Apache 内部换资源
Redirect = 告诉浏览器换地址重新请求
Proxy = Apache 自己替浏览器请求其他服务器
本文地址: https://blog.fbzl.org/apache-rewrite-重定向问题汇总/ 资料收集