Nginx Location 加不加 / 的问题

近期在配置Nginx的时候,总是晕这个 Location / 如何去配置,怎么配置访问到什么,怎么配置才是正确的,每次都要纠结一下,所以这次我干脆搭建一个后端接收Nginx反向代理,然后输出真实请求路径。

(English version translate by GPT-3.5)

最近项目的确碰到蛮多用nginx的地方,但是nginx这个 location /xxx 的问题着实头痛,虽然网上资料一堆,但是每次去查下还不一定直接选到最适合自己的,还不如自己写一份呢。

在Nginx的官网中也有详细介绍 Module ngx_http_proxy_module - nginx.org,下面开始,文中的 上面 指代 Location部分,而 下面 指的是proxy_pass部分

常规请求

这个请求没啥好说的,就是进来啥就转发到啥

1
2
3
location / {
proxy_pass http://127.0.0.1:8000;
}

测试1

1
2
请求:http://127.0.0.1:9999/path1/path2/path3/maven2/io/netty/netty.jar
实际地址:http://127.0.0.1:8000/path1/path2/path3/maven2/io/netty/netty.jar

包括这样子配置也是一样的

1
2
3
location /test {
proxy_pass http://127.0.0.1:8000;
}

测试2

1
2
请求:http://127.0.0.1:9999/test/netty.jar
实际地址:http://127.0.0.1:8000/test/netty.jar

这句话在官网解释如下

If proxy_pass is specified without a URI, the request URI is passed to the server in the same form as sent by a client when the original request is processed, or the full normalized request URI is passed when processing the changed URI

即,如果proxy_pass中没有定义URI,那么请求的地址将完整的传给后端,所以如果只是将 /api 请求到后端的 /api,这样配置就行

1
2
3
location /api {
proxy_pass http://127.0.0.1:8000;
}

两边都带 /

在这个情况下,请求会将/test/去掉,直接将后面都URL请求到后端,如下所示

1
2
3
4
5
6
location /test/ {
proxy_pass http://127.0.0.1:8000/;
}

请求:http://127.0.0.1:9999/test/netty.jar
实际地址:http://127.0.0.1:8000/netty.jar

在官网中有这句话

If the proxy_pass directive is specified with a URI, then when a request is passed to the server, the part of a normalized request URI matching the location is replaced by a URI specified in the directive

如果 proxy_pass 是用一个URI指定的,那么当请求传递到后端时,与这个位置匹配的规范化请求地址将会被指令中指定的URL替换。。。呃,我语文时不及格的。。。反正就是多试几个感受下

测试1 前短后长

1
2
3
4
5
6
location /test/ {
proxy_pass http://127.0.0.1:8000/central/maven2/;
}

请求:http://127.0.0.1:9999/test/io/netty.jar
实际地址:http://127.0.0.1:8000/central/maven2/io/netty.jar

测试2 前长后短

1
2
3
4
5
location /private/maven-repo/maven2/ {
proxy_pass http://127.0.0.1:8000/maven-repo/;
}
请求:http://127.0.0.1:9999/private/maven-repo/maven2/io/netty.jar
实际地址:http://127.0.0.1:8000/maven-repo/io/netty.jar

测试3 前长后稍长

1
2
3
4
5
location /private/maven-repo/maven2/ {
proxy_pass http://127.0.0.1:8000/maven-repo/central/;
}
请求:http://127.0.0.1:9999/private/maven-repo/maven2/io/netty.jar
实际地址:http://127.0.0.1:8000/maven-repo/central/io/netty.jar

结论

从上面2个测试中,它说的指令就是下面的proxy_pass中的URL,2边都带 / 的请求,就会将上面 location /xxxxx/yyy/ 中的 /xxxxx/yyy/ 给全部替换掉,替换成下面proxy_pass的地址后面的所有路径,例如上面 location /private/maven-repo/maven2/ 转发到 http://127.0.0.1:8000/maven-repo/, 它就会将 /private/maven-repo/ 给替换成 /maven-repo/ 然后请求到后端

含Path且上面带 /,下面不带 /

与常规不同,这里下面不带 / 指的是含path的情况,根据上面这一个测试,应该能得出结论了

1
2
3
4
5
6
7
8
9
10
11
location /private/maven-repo/maven2/ {
proxy_pass http://127.0.0.1:8000/maven-repo/central;
}

请求:http://127.0.0.1:9999/private/maven-repo/maven2/io/netty.jar
实际地址:http://127.0.0.1:8000/maven-repo/centralio/netty.jar
注意上面的central和io合在一起了

下面的请求,maven2和io之间用了2个/
请求:http://127.0.0.1:9999/private/maven-repo/maven2//io/netty.jar
实际地址:http://127.0.0.1:8000/maven-repo/centralio/netty.jar

按照上面的逻辑,它就是直接把 /private/maven-repo/maven2/ 给替换成了 /maven-repo/central,然后请求到后端,这里可能就会出现拼接后的地址被合并的情况。

含Path,上面不带/,下面带/,以及上下都不带 /

这其实也是可以按照上面的逻辑完成

测试1,上面不带 / 下面带 /

1
2
3
4
5
6
location /private/maven-repo/maven2 {
proxy_pass http://127.0.0.1:8000/maven-repo/central/;
}
请求:http://127.0.0.1:9999/private/maven-repo/maven2/io/netty.jar
实际地址:http://127.0.0.1:8000/maven-repo/central//io/netty.jar
注意上面的地址,central和io之间有2个 /

测试2,上面下面都不带 /

1
2
3
4
5
location /private/maven-repo/maven2 {
proxy_pass http://127.0.0.1:8000/maven-repo/central;
}
请求:http://127.0.0.1:9999/private/maven-repo/maven2/io/netty.jar
实际地址:http://127.0.0.1:8000/maven-repo/central/io/netty.jar

果然也是一个替换过程,对于上面带/下面不带/,就是将 /private/maven-repo/maven2 替换成了 /maven-repo/central/,然后直接加上后面的参数,就变成了 /maven-repo/central//io/netty.jar 的情况,对于2边都不带 / 的情况,就是将 /private/maven-repo/maven2 替换成了 /maven-repo/central

所以说,如果只有一个 / 的情况下,那么它将会这样请求

1
2
3
4
5
6
7
8
location / {
proxy_pass http://127.0.0.1:8000/central/repo;
}

请求:http://127.0.0.1:9999/maven2/io/netty.jar
将 / 替换成 /central/repo,然后加上后面的maven2/io/netty.jar
后端地址:http://127.0.0.1:8000/central/repomaven2/io/netty.jar
注意上面repo和maven2合并了

总结

其实上述测试中,就2个情况,直接地址代理和自动替换方式

  1. 当proxy_pass里面直接填上Host,后面没有任何路径时,请求进来的是什么,就会给后端发什么

    1
    2
    location /aa/bb ->  请求 /aa/bb/cc.jar
    后端地址:/aa/bb/cc.jar
  2. 如果proxy_pass添加了任何Host,并没有任何参数时,那么会将location 后面的所有内容替换成proxy_pass的URL地址

    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
    情况一
    location /jcenter/maven2/
    proxy_pass https://jcenter.bintray.com/
    会将请求中的 /jcenter/maven2/ 替换成 /
    例如 /jcenter/maven2/io/netty.jar 会请求到 https://jcenter.bintray.com/io/netty.jar

    情况二
    如果location 结尾没有 /,但是proxy_pass后面有 /,就会出现 // 的情况
    location /jcenter/maven2
    proxy_pass https://jcenter.bintray.com/
    请求:/jcenter/maven2/io/netty.jar
    首先将 /jcenter/maven2 替换成 /,然后拼接后面的内容,实际请求就变成了
    https://jcenter.bintray.com//io/netty.jar

    情况三
    这个情况对于location中结尾有/,proxy_pass没有 / 结果是一样的
    location /maven2/
    proxy_pass https://repo1.apache.org/maven2
    对于请求 /maven2/io/netty.jar
    按照规则将 /maven2/ 替换成/maven2, 然后拼接后面的字符,就变成了
    https://repo1.apache.org/maven2io/netty.jar

    情况四,与情况2类似,就是都是有 / 的情况下
    这个情况对于location中结尾有/,proxy_pass也有 / 结果和情况2是一样的
    location /maven2/
    proxy_pass https://repo1.apache.org/maven/maven2/
    对于请求 /maven2/io/netty.jar
    按照规则将 /maven2/ 替换成/maven/maven2/, 然后拼接后面的字符io/netty.jar,就变成了
    https://repo1.apache.org/maven/maven2/io/netty.jar
    但是这里如果请求 /maven2,就会出现自动加 / 的问题,这个自行查阅下吧,很容易理解的。(server_name_in_redirect以及port_in_redirect以及absolute_redirect,知道这3个,就知道怎么解决了。)