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 阶段处理,常用于配合 AliasScriptAlias 等。
  • 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]

ENDL 更彻底,尤其适用于:

.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-重定向问题汇总/