Nginx Rewrite

Nginx Rewrite


网站在使用 Nginx 时都会进行个性化配置满足自己的业务需要,而 URL 重写几乎是每个网站都必做的事情,Nginx 的 URL 重写规则不像 Apache 那样简单直接,逻辑相对要复杂一些,本文将通过例子的方式帮助大家理解 Nginx rewrite 原理,希望能对您有些启发。

Nginx 中重定向的多种方式

外部重定向

return 指令返回 301 或 302(return 也可以返回其他状态码),可以放在 server 或 location 块中。例如:

1
2
3
return 301 https://www.mi.com;
or
return 302 https://www.mi.com;

还可以使用 rewrite 指令,例如:

1
2
3
rewrite ^/(.*)$   http://www.mi.com/$1;
or
rewrite ^/(.*)$ http://www.mi.com/ redirect;

内部重定向

return + error_page 指令的组合,或 try_files 指令和 rewrite 指令,非常灵活。

本文主要讲解 rewrite 的工作原理,其他指令的使用方法大家可以自行查阅 Nginx 官网。在使用 Nginx 的 rewrite 指令时,flag 可以设置为 last 和 break,这两个 flag 很容易混淆,后面我们会比较这两个 flag 的区别,下面通过示例我们来认识一下 rewrite 指令。

rewrite 语法

1
2
Syntax:    rewrite regex replacement [flag];
Context: server, location, if
  • regex: 对请求的 URI 做正则匹配

  • replacement:目标 uri 匹配成功后替换的 url

  • 可以使用的 flag 有以下 4 个(flag 也可以为空):

    • redirect:返回 302 临时重定向,客户端地址栏会显示跳转后的地址;

    • permanent:返回 301 永久重定向,客户端地址栏会显示跳转后的地址;

    • last:内部重定向,停止处理后续 rewrite 模块中的指令(客户端无感知);

    • break:内部重定向,停止处理后续 rewrite 模块中的指令(客户端无感知)。

NOTE:

  1. regex 匹配的是 uri,不包含 hostname 和 query string,默认 query string 是被追加到 replacement 末尾,如果不希望在末尾追加请求的 query string,可以在 replacement 的末尾加一个 “?”。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
rewrite ^/mi_one/(.*)$ /mi/$1;
rewrite ^/mi_two/(.*)$ /mi/$1?x=0&y=1?;

location /mi/ {
echo $uri$is_args$args;
}
}

$ curl "http://localhost/mi_one/hello?a=1&b=2"
output: /mi/hello?a=1&b=2

$ curl "http://localhost/mi_two/hello?a=1&b=2"
output: /mi/hello?x=0&y=1
  1. 如果 replacement 是以 “http://“,”https://“ 或 $scheme” 开始的字符串,那么 rewrite 指令停止后面的处理,直接返回给客户端,没有指定 flag 时,与 redirect 效果相同。

  2. 没有 flag 的 rewrite 指令根据出现的顺序执行,flag 可以控制指令的执行顺序。

  3. 在配置中开启 rewrite_log 指令,日志文件中会记录 rewrite 的匹配过程,有助于调试 rewrite 问题。

Nginx 请求处理流程

在讲 rewrite 前我们先来简单了解下 Nginx 请求处理流程,为什么需要了解请求处理流程呢?因为 rewrite 操作与其中几个 phase 关系很密切,熟悉了请求处理流程,理解 rewrite 执行逻辑就会很容易。在 Nginx 内部将请求处理划分为 11 个 phase,每个 phase 会执行对应的 handler,这里我们不打算逐个进行讲解。在 11 个 phase 中与 rewrite 指令逻辑有关的只有 4 个,所以在本文我们主要关注 SERVER_REWRITE、FIND_CONFIG、REWRITE 和 POST_REWRITE 这四个 phase。

首先我们要清楚的是:

  • server 块中的 rewrite 模块指令在 SERVER_REWRITE 阶段解析;

  • location 块中的 rewrite 模块指令在 REWRITE 阶段解析;

  • SERVER_REWRITE - 请求到达后首先处理这个阶段的 rewrite 指令操作

  • FIND_CONFIG - 根据 SERVER_REWRITE 阶段得到的 uri 查找 location

  • REWRITE - 确定 location 后执行 locaton 中 rewrite 操作

  • POST_REWRITE - 根据上一阶段的 uri 重写结果做决策,可能跳回 FIND_CONFIG 阶段重新查找 location,也可能继续执行后边的 phase。例如:在 location 中配置了 rewrite 指令并且指定 flag=break,执行完本条 rewrite 终止后边的 rewrite 匹配,然后执行 PREACCESS 阶段中的 handler。同样的场景下 flag=last,执行完本条 rewrite 终止后边的 rewrite 匹配,然后跳到 FIND_CONFIG 阶段再次查找 location。未指定 flag 的情况与 flag=last 类似,唯一区别是在同一层级中未指定 flag 的 rewrite 语句不会终止后续的 rewrite 匹配。

通过例子理解 rewrite 指令

1.未指定 flag

未指定 flag 的 rewrite 会按照出现顺序进行匹配,server 块中 rewrite 匹配完以后根据改写的 uri 查找 location,然后再匹配 location 中的 rewrite,location 中的 rewrite 指令匹配成功后会再次查找 location。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server {
rewrite ^/(.*)$ /mi_one/$1;
rewrite ^/mi_one/(.*)$ /mi_two/$1;

location / {
echo "This is default location";
}

location /mi_one/ {
echo "This is mi_one location";
}

location /mi_two/ {
echo "This is mi_two location";
}
}

$ curl http://localhost/mi_zero/hello
This is mi_two location

说明:

  • 匹配第一条 rewrite 成功,uri 被改写为 / mi_one/mi_zero/hello,没有指定 flag 的 rewrite 继续匹配后面的 rewrite

  • 匹配第二条 rewrite 成功,此时 uri 被改写为 / mi_two/mi_zero/hello

  • 查找 location,mi_two 被确定为最终的 location

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    server {
    rewrite ^/(.*)$ /mi_one/$1;

    location / {
    echo "This is default location";
    }

    location /mi_one/ {
    rewrite ^/mi_one/(.*)$ /mi_two/$1;
    echo "This is mi_one location";
    }

    location /mi_two/ {
    echo "This is mi_two location";
    }
    }

    $ curl http://localhost/hello
    This is mi_two location

说明:

  1. 匹配 server 块中的 rewrite 成功,uri 被改写为 / mi_one/hello

  2. server 块中只有一条 rewrite 指令,开始查找 location

  3. location mi_one 被找到,开始匹配 location 中 rewrite

  4. location 中的 rewrite 匹配成功,uri 被改写为 / mi_two/hello

  5. 再次查找 location,mi_two 被确定为最终使用的 location

再来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server {
rewrite ^/(.*)$ http://www.mi.com/$1;
rewrite ^/mi_one/(.*)$ /mi_two/$1;

location / {
echo "This is default location";
}

location /mi_one/ {
echo "This is mi_one location";
}

location /mi_two/ {
echo "This is mi_two location";
}
}

$ curl -I http://localhost/mi_one/hello
http code: 302
Location: http://www.mi.com/mi_one/hello

说明:
匹配第一条 rewrite 成功,由于 replacement 是以 http:// 开始的字符串,所以 rewrite 指令直接返回给客户端 302,并且停止匹配后续的 rewrite。

2.flag 指定为 redirect

指定 flag 为 redirect 时,rewrite 匹配成功后直接返回给客户端 302,不会继续匹配后续的 rewrite。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
server {
rewrite ^/(.*)$ /mi_one/$1 redirect;
rewrite ^/mi_one/(.*)$ /mi_two/$1;
rewrite ^/mi_two/(.*)$ /mi_three/$1;

location / {
echo "This is default location";
}

location /mi_one/ {
echo "This is mi_one location";
}

location /mi_two/ {
echo "This is mi_two location";
}

location /mi_three/ {
echo "This is mi_three location";
}
}

$ curl -I http://localhost/mi_zero/hello
http code: 302
Location: http://localhost/mi_one/mi_zero/hello

$ curl -I http://localhost/mi_one/hello
http code: 302
Location: http://localhost/mi_one/mi_one/hello

$ curl -I http://localhost/mi_two/hello
http code: 302
Location: http://localhost/mi_one/mi_two/hello

再来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
server {
rewrite ^/mi_one/(.*)$ /mi_two/$1;
rewrite ^/mi_two/(.*)$ /mi_three/$1 redirect;
rewrite ^/(.*)$ /mi_one/$1;

location / {
echo "This is default location";
}

location /mi_one/ {
echo "This is mi_one location";
}

location /mi_two/ {
echo "This is mi_two location";
}

location /mi_three/ {
echo "This is mi_three location";
}
}

$ curl -I http://localhost/mi_one/hello
http code: 302
Location: http://localhost/mi_three/hello

$ curl -I http://localhost/mi_two/hello
http code: 302
Location: http://localhost/mi_three/hello

$ curl http://localhost/mi_zero/hello
This is mi_one location

3.flag 指定为 permanent

指定 flag=permanent 时,与 redirect 效果相同,唯一的区别 http code 返回 301。

4.flag 指定为 last

rewrite 的 last 和 break 这两个 flag 使用场景很多并且也很容易混淆,他们的共同点都会停止当前层级后续的 rewrite 匹配,区别需要分两种情况:第一种使用在 server block 中,last 和 break 没有区别。第二种使用在 location block 中,last 会根据改写的 uri 重新查找 location,break 不会重新查找 location,而是在当前 location 中执行后续的指令。(注:last 和 break 不仅停止 rewrite 的匹配,同时还会停止 Nginx rewrite 模块中其他指令的执行,例如:set、return 指令)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
rewrite ^/mi_one/(.*)$ /mi_two/$1 last;
rewrite ^/mi_two/(.*)$ /mi_three/$1;

location / {
echo "This is default location";
}

location /mi_one/ {
echo "This is mi_one location";
}

location /mi_two/ {
echo "This is mi_two location";
}

location /mi_three/ {
echo "This is mi_three location";
}
}

$ curl http://localhost/mi_one/hello
This is mi_two location

说明:

  1. 匹配第一条 rewrite 成功,uri 被改写为 / mi_two/hello,由于 flag 指定为 last 会停止后续的 rewrite 的匹配(仅停止 server block 中的 rewrite 匹配),所以会根据改写的 uri 查找 location

再来看一个 last 在 location block 中使用的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server {
rewrite ^/(.*)$ /mi_one/$1;

location / {
echo "This is default location";
}

location /mi_one/ {
rewrite ^/(.*)$ /mi_three/$1 last;
rewrite ^/mi_three/(.*)$ /;

echo "This is mi_one location";
}

location /mi_two/ {
echo "This is mi_two location";
}

location /mi_three/ {
echo "This is mi_three location";
}
}

$ curl http://localhost/hello
This is mi_three location

说明:

  1. 匹配 server block 中第一条 rewrite 成功,uri 被改写为 / mi_one/hello

  2. 查找 location,mi_one 被确定为使用的 location

  3. 匹配 location block 中的 rewrite,location 中的第一条 rewrite 匹配成功,uri 被改写为 / mi_three/mi_one/hello,由于 flag 指定为 last,所以停止 location 中后续的 rewrite 匹配,此时再根据 uri=/mi_three/mi_one/hello 查找 location,最终 mi_three 被确定为使用的 location

5.flag 指定为 break

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
rewrite ^/mi_one/(.*)$ /mi_two/$1 break;
rewrite ^/mi_two/(.*)$ /mi_three/$1;

location / {
echo "This is default location";
}

location /mi_one/ {
echo "This is mi_one location";
}

location /mi_two/ {
echo "This is mi_two location";
}

location /mi_three/ {
echo "This is mi_three location";
}
}

$ curl http://localhost/mi_one/hello
This is mi_two location

说明:

  1. 匹配 server block 中第一条 rewrite 成功,uri 被改写为 / mi_two/hello,由于 flag 指定为 break 所以会停止 server block 中后续的 rewrite 匹配,根据 uri=/mi_two/hello 查找 location,最终 mi_two 被确定为使用的 location

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    server {
    rewrite ^/(.*)$ /mi_one/$1;

    location / {
    echo "This is default location";
    }

    location /mi_one/ {
    rewrite ^/(.*)$ /mi_three/$1 break;
    rewrite ^/mi_two/(.*)$ /;

    echo "This is mi_one location";
    echo "uri: ${uri}";
    }

    location /mi_two/ {
    echo "This is mi_two location";
    }

    location /mi_three/ {
    echo "This is mi_three location";
    }
    }

    $ curl http://localhost/hello
    This is mi_one location
    uri: /mi_three/mi_one/hello

    说明:

  2. 匹配 server block 中第一条 rewrite 成功,uri 被改写为 / mi_one/hello

  3. 根据 uri=/mi_one/hello 查找 location,mi_one 被确定为使用的 location

  4. 匹配 location block 中的 rewrite,location 中的第一条 rewrite 匹配成功,uri 被改写为 / mi_three/mi_one/hello,由于 flag 指定为 break,所以停止 location 中后续的 rewrite 匹配,并且把当前 location 作为最终使用的 location,不会重新查找 location(last 会继续查找 location)

总结

在 Nginx 的配置中可以实现简单的编程,理解起来相对有点难度,通过阅读此文希望能对你有些启发,能够根据项目需求可以配置更复杂的 rewrite 规则。想要更深入的理解 rewrite,还需要大家自己动手实践。

FYI

https://github.com/agile6v/awesome-nginx

http://nginx.org/en/docs/http/ngx_http_rewrite_module.html

https://www.nginx.com/blog/creating-nginx-rewrite-rules/

https://www.nginx.com/blog/converting-apache-to-nginx-rewrite-rules

http://www.thegeekstuff.com/2017/08/nginx-rewrite-examples/

http://winginx.com/en/htaccess

https://w3techs.com/technologies/overview/web_server/all

http://nginx.org/en/docs/dev/development_guide.html#httpphases

本文标题:Nginx Rewrite

文章作者:shuke

发布时间:2020年04月20日 - 15:04

最后更新:2020年04月20日 - 15:04

原始链接:https://shuke163.github.io/2020/04/20/Nginx-Rewrite/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------本文结束感谢您的阅读-------------

本文标题:Nginx Rewrite

文章作者:shuke

发布时间:2020年04月20日 - 15:04

最后更新:2020年04月20日 - 15:04

原始链接:https://shuke163.github.io/2020/04/20/Nginx-Rewrite/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%