nginx的location与proxy_pass指令超详细讲解及其有无斜杠( / )结尾的区别

本文所使用的环境信息如下:

  • windows11 (主机系统)
  • virtual-box-7.0环境下的ubuntu-18.04
  • nginx-1.22.1 (linux)

斜杠结尾之争

实践中,nginx里最常用的指令就是locationproxy_pass了。前者用于为不同请求uri指定不同nginx配置,后者用于匹配的location进行转发(通常是动态内容)。关于二者的配置,有一个老生常谈的话题,那便是:配置的值是否有斜杠结尾,对文件路径查找(或请求转发)行为有哪些影响?相关文章也非常多,且多数粗看一眼,照其行事,也能立即解决问题。鄙人私以为其中部分文章的说法是不严谨的,故特撰此文,以备己查。

结论

不再废话,直接上结论(如果对location和proxy_pass的功能和基本配置还不熟悉,建议先看后面的章节):

  • location 指令有斜杠 / 结尾的情况

    一般情况下,location 指令不会对是否有斜杠结尾这个场景做特殊处理,除非满足以下条件:

    满足以上条件后,也只会对一个特定的 uri 做特殊处理,这个 uri 除了没有尾部的斜杠外,正好与 location 定义的前缀一模一样。对这个特殊的uri的处理方式为:返回一个301重定向,重定向的地址为:原始请求 uri + /,也就是说,重定向地址与 location 的前缀内容完全相同

    示例配置如下:

    location /films/nature/ { 
        proxy_pass http://film-server;
    }
    

    假定请求的 url 为http://localhost/films/nature,则 location 的处理方式为:返回一个301重定向,重定向的地址为http://localhost/films/naure/。与原始请求的唯一差别就是,新的 uri 地址比原来的 uri 地址尾部多了一个斜杠 / 😁

    是的,你没看错,对 location 的配置里,其最后一个字符为斜杠的特殊处理,就仅此而已了。用大白话讲就是:嘿,客户端伙计,你这家伙不讲武德呀,你请求的uri在我们(服务器端)看来就是一个目录呀,那你咋不在尾部加上目录符号(即 / )呢?念你初始犯,从轻发落,打回本次请求,将格式改正确后再重新发过来(即301重定向)。

    上面这个特殊操作,有很多限制条件,最终也只作用于一个特殊的 uri 。如果请求的 url 为http://localhost/films/nature/cheetah.mp4,它同样与上面的 loation 匹配,但不会发起301重定向。但如果我们就是要访问http://localhost/films/nature 这个资源,并要求服务器不要返回重定向,该如何处理呢?可以使用精确匹配来解决这个问题,就像下面这样配置:

    location /films/nature/ { 
        proxy_pass http://film-server;
    }
    location = /films/nature {           # 通过精确匹配,可避免重定向
        proxy_pass http://film-server;
    }
    

    很显然,这个十分独特的场景和处理方式,我们基本上是无感知的,并且绝大多数情况下,业务也不需要对这个特殊的场景做特别的处理,因此,完全可以忽略这个特性的存在。

    🍁 特别说明
    通过实测发现,location指令还会对一类特别的uri做重定向处理(这个特性没有在官方文档上注明),这类uri的特点是:

    • uri最后一个字符不是斜杠 /
    • 整个uri正好指向location内root指令目录下的某个子目录

    简而言之,就是一个uri在服务器端正好指向了一个目录,但这个uri却没有以目录符号/结尾。

    比如有下面这样一个配置(点击查看):
    server {
      listen 80;
    
      location /books/ {
        root /var/www/book-store;
      }
    }
    

    假定服务器上有 /var/www/book-store/books/ 和 /var/www/book-store/books/society/ 这两个目录,当访问http://localhost/books 和http://localhost/books/society 时,都会返回301重定向,且重定向的地址为http://localhost/books/ 和http://localhost/books/society/

    这看上去很合理,但如果请求是通过代理类指令(参见①处)转发过来时,很可能引发域名解析问题,最终导致访问不了。因为重定向的host就是请求的host,这在直接访问时没有问题,但对于通过代理指令转发过来的请求,其host已不再是初始请求的host,通常都是内部的虚拟主机或主机群组(用于负载均衡)名称,这些名称外部是不可解析的,从而会在最初的请求客户端引发域名解析问题。下面举一个稍微复杂一点的例子来说明:

    示例配置如下(点击查看):
    server { 
      listen 80;
      location /film/ {                 ⑴
        proxy_pass http://film-server;  
      }
    }
    
    upstream flim-server {
      server localhost:8379;
    }
    
    server {
      listen 8379;
      location / {                      ⑵
        root /var/www/film-doc;
        index index.html;
      }
    }
    

    假定服务器上存在 /var/www/film-doc/film/ 和 /var/www/film-doc/film/war/ 两个目录,当访问http://localhost/film/war 时,会匹配到⑴处,然后会发起代理,地址为http://film-server/film/war,这个地址将通过 upstream 最终执行到⑵处。由于这个 uri 正好对应磁盘目录 /var/www/film-doc/film/war/,但却没有以目录符号/结尾,因此⑵处的这个server将返回一个301重定向响应。

    对于⑵处的这个 server 而言,它收到的请求为http://film-server/film/war,其主机名 film-server 已经不是最初的 localhost 了,因此返回的重定向地址为http://film-server/film/war/。客户端再次发起http://film-server/film/war/时,会解析不了 film-server 这个主机名,因为它是一个 nginx 内部虚拟的主机群的名字,最终原始的http://localhost/film/war 请求会失败,浏览器上表现为接收不到任何响应,通过F12打开调试面板,可以看到,浏览器收到了一次重定向响应,并根据重定向地址再次发起了新的请求。

    有趣的是,如果客户端发起http://localhost/film 这个请求会怎么样呢,会不会也失败呢?

    答案是不会失败,这个请求的执行流程如下:

    • 请求匹配到⑴处,尽管/film并不与/film/匹配(少了末尾的斜杠),但根据前面②处的规则,将返回301重定向,地址为http://localhost/film/
    • 客户端收到重定向响应后,发起新的请求,地址为http://localhost/film/
    • 请求与⑴处完全匹配,发起内部代理请求,地址为http://film-server/film/
    • 代理请求与⑵处匹配,由于服务器上正好存在 /var/www/film-doc/film/ 这个目录,且请求的 uri 是以目录符号/结尾的,因此不会再返回301重定向响应了
    • 接下来会检查 /var/www/film-doc/film/ 目录下是否存在index.html文件,如果有则返回,没有则返回403 Forbidden响应(返回403是默认行为,如果 location 内添加了autoindex on指令,即允许访问目录,则会返回一个目录列表页面)。
  • proxy_pass 指令有斜杠 / 结尾的情况

    proxy_pass 指令不会对其配置是否有斜杠结尾做任何特殊处理

    更严格而准确地说法是:proxy_pass 指令会针对其配置是否包含有 uri (这里的 uri 是指 url 中,端口与问号之间的部分)做特殊处理,具体如下

    • 不带uri时(如http://localhost:8379)
      新的地址构成为:proxy_pass的配置内容 + 原始请求uri中去除掉协议、主机和端口后的剩余内容

    • 配置了uri时(如http://localhost:8379/ 或http://localhost:8379/foo
      新的地址构成为:proxy_pass的配置内容 + 原始请求 uri 中去除掉协议、主机、端口和location配置内容后的剩余部分。上述两个 url 中,//foo 就是这里说的 uri

    从上面可以看出,proxy_pass在创建新的转发地址时,总是会剔除掉原始uri中的协议、主机、端口。核心差异在于是否要去除掉location指令的配置内容。如果proxy_pass配置带有uri就去除,反之则不去除。

    另外,像http://localhost:8379/这个地址很特别,因为去除掉协议、主机、端口后,就只剩下 / 了,这大概就是“以斜杠结尾的 proxy_pass 会去除掉 location 的配置内容”这个错误说法的源头了。事实上,像http://localhost:8379/foo这个地址,uri 为 /foo,它并没有以 / 结尾,但在生成新的转发 uri 时,同样会去除掉 location 的配置内容。

实例演示

上面只是对location和proxy_pass两个指令的斜杠结尾之争这个误会,从理论上做出了解释。但还不形象,不易理解,下面给出一个实例,以加深理解。
  
location中无proxy_pass的情况

示例配置如下(点击查看):
location /books/ {               # 配置A:以 / 结尾
    root /var/www/mall/;
    ...
}

location /shop/goods {           # 配置B:无 / 结尾
    root /var/www/mall/sell;
    ...
}

location /store {
    root /var/www/mall/store;    # 配置C:根目录的最后一级,名称与location的前缀配置内容一致
    ...
}

下面是几个不同的uri,所匹配的location,以及它们对应的资源文件在服务器磁盘上的路径位置。

请求 匹配的location 文件位置
http://localhost/books/red-rock.html 配置A /var/www/mall/books/red-rock.html
http://localhost/shop/goods/keep-alive.html 配置B /var/www/mall/sell/shop/goods/keep-alive.html
http://localhost/store/yalu-river.html 配置C /var/www/mall/store/store/yalu-river.html
http://localhost/store-central.html 配置C /var/www/mall/store/store-central.html

简而言之,这种情况下,资源文件在磁盘的路径为:location配置的根目录 + uri。可见,location配置结尾的 / 字符,并没有特别之处,就是一个普通的字符。

location中有proxy_pass的情况

示例配置如下(点击查看):
location /films/ {
    proxy_pass http://film-server/;               # 配置A:带了一个最简短uri
}
location /comments/ {
    proxy_pass http://comment-server;             # 配置B:不带uri
}
location /comments/top/ {
    proxy_pass http://comment-server/top/;        # 配置C:指定了uri,且以 / 结尾
}
location /feedbacks/ {
    proxy_pass http://feekback-server/center      # 配置D:是一个稍微长一点的uri,但没有以 / 结尾
}

下面是一组WEB请求的转发情况

请求 匹配的location 生成的代理地址 说明
http://localhost/films/wandering-earth 配置A http://film-server/wandering-earth 由于proxy_pass的配置指定了uri,新的代理地址将不包含location的配置(即/films/)
http//localhost/comments/list 配置B http://comment-server/comments/list 由于proxy_pass的配置不带uri,新的代理地址会保留location的配置(即/comments/)
http://localhost/comments/top/asc100 配置C http://comment-server/top/asc100 新的代理地址将不包含location的配置, 其中的top来自proxy_pass的配置,而非location
http://localhost/comments/top/..//desc50 配置B http://comment-server/comments/desc50 请求的uri经过规范化处理后变成了http://localhost/comments/desc50,然后才开始匹配,因此匹配的location为配置B
http://localhost/feedbacks/list 配置D http://comment-server/centerlist 新的代理地址将不包含location的配置, 由于proxy_pass的配置末尾没有斜杠,所以直接拼接后,会得到centerlist这个串

location指令配置详解

location指令是ngx_http_core_module中的一个最常用的指令,它将不同的请求uri映射到不同的Web资源配置。

🍁 特别说明:location指令匹配的是请求uri,而不是整个请url,这里的uri是url中端口与问号(?)之间的部分。比如http://localhost:1983/books/list?author=jane这个URL,参与匹配的uri为/books/list

  • 指令格式
    有两种语法格式,如下:

    • location [ = | ~ | ~* | ^~ ] uri { ... } 
    • location @name { ... } 
  • 适用范围
    在server指令内,或location指令内(即location指令是可以自嵌套的)

  • 功能用途
    根据匹配的uri查找服务器上的磁盘文件,再以合适的web形式返回,或代理(转发)到其它web服务(这需要代理类指令配合,如proxy_pass)

location指令在功能层面上的流程如下

                  location内是否有代理类指令
               +--------------------------------+ 
WebRequest --> | Does contains proxy instruct ? |---- No ---> 返回服务器磁盘上的文件  ★
               +--------------------------------+
                               |
                              Yes
                               |
                               +---------> 代理到其它WEB服务

本小节只介绍location内没有代理类指令的情况,即上面流程中标★处。有代理的情况,将在prxoy_pass指令中详细阐述。

模板location

模板型location,即 location [ = | ~ | ~* | ^~ ] uri { ... } 这种语法,它共有四部分组成,分别是:

  • location关键字:这个没什么好说的,就是location指令的标识
  • uri匹配模式:也叫匹配修饰符,它表明以什么方式来匹配请求的uri,共5种,分别是:空、= 、~ 、~* 、^~
  • uri匹配样式:也叫uri模板,它是各个具体的匹配模式要使用的uri样式内容

location的匹配模式分为三类,分别是:前缀匹配精确匹配正则匹配,详细说明如下:

  • 无或空
    即不指定任何匹配模式符号,此时,它代表前缀匹配。比如 location /books/ { ... },它可以匹配/books/index.html、/books/computer/GitDefinitiveGuide.pdf

  • =
    符号 = 代表精确匹配,要求请求的uri与该符号后的uri样式完全一样,比如 location = /books { ... },它可以精确的匹配/books这样的uri,像/books/、/books/index.html、/books.doc、/booksmark.pdf这样的uri均无法匹配。

    精确匹配一旦成功,则整个匹配过程结束,不再继续尝试匹配其它的location

  • ~
    符号 ~ 代表正则表达式匹配,并且是区分大小的。比如 location ~ \.(gif|jpg|PNG)$ { ... },它可以匹配/red-rock.jpg和/img/pigion.gif,但不能匹配/img/greatwall.png,因为这里的png是小写的,而 ~ 匹配的字符是大小写敏感的。

  • ~*
    符号 ~* 代表正则表达式匹配,并且它不区分大小的。比如 location ~* \.(gif|jpg|PNG)$ { ... },它可以匹配/red-rock.jpg和/img/pigion.gif,或可以/img/greatwall.png,尽管这里的png是小写的,但 ~* 匹配不区分大小写,依然可以匹配。

  • ^~
    符号 ^~ 代表的匹配规则很特别,它看上去像是一个正则匹配,实则不是,它依然代表的是前缀匹配,与默认的前缀匹配(即没有任何符号的那种)的区别是:假定一个server内配置了多个前缀型的location和多个正则location,如果这些前缀location中,最终匹配的location是一个 ^~ 的话,则不再尝试后续的正则匹配。

    简而言之,^~就是一个禁止做正则匹配的前缀匹配,从它的功能定义上来看,这个^~符号改成!~更形象些,毕竟感叹号!就代表否定的意思,而^本身就是一个正则表达式的特殊符号,很容易引起误会。

location指令的处理流程,总体上分类三个阶段,分别是:uri规范化处理、uri匹配、后置处理,详细说明如下:

  1. uri规范化处理
    这一步是处理原始uri串中不规范的内容,将其规范花后,方便后续做匹配,这些处理包括:

    • 解码 &xx 这样的url编码字符
    • 解析 . 和 .. 到这些符号所引用的目录上,比如/comment/top/../top100,处理后会变成/comment/top100
    • 将多个连续的/压缩成一个,比如/films/science-fiction///wandering-earth,处理后会变成/films/science-fiction/wandering-earth
  2. uri匹配
    uri匹配有前面提到的三种类型,即:精确匹配、前缀匹配和正则匹配。假定一个server配置中,有多个精确匹配的location、多个前缀匹配的location和多个正则匹配的location。则整个匹配流程是这样的:

    • 先做精确匹配,按照精确匹配location在配置文件中的出现顺序进行,一旦命中一个,则整个匹配过程结束。

    • 若精确匹配没有命中,则执行前缀匹配,按照配置文件中前缀location出现的顺序执行,如果命中多个,则只记录location配置内容最长的那个。
      假定有两个前缀配置分别是:location /films/ { ... } 和 location /films/nature/ { ... }。请求地址/films/nature/aerial-view-of-china.mp4与这两个前缀locaton配置均能匹配,但最终将只保留第二个匹配,因为它的配置前缀(即/films/nature)最长。由此可以推断出,默认的配置(即 location / { ... } )一定是兜底的匹配,当所有其它类型的匹配均未命中时,它一定能命中。

    • 接着再按照配置文件中的顺序,执行正则匹配,与前缀匹配不同的是:一旦命中一个正则匹配,整个匹配就结束了,不再对后面的正则location进行匹配。并且整个匹配的结果就是这个正则location。如果没有一个正则命中,则整个匹配的结果就是上面前缀匹配中,记录的那个location内容最长的命中结果。
      假定有两个正则配置分别是:location ~* \.(jpg|gif|png)$ { ... } 和 location ~* \.(png|jpeg|svg)$ { ... }。请求地址/img/logo.png将只会与第一个匹配,尽管它也满足第二个正则表达式,但由于第一个正则的位置在前,并且匹配成功,因此就不再进行后面的匹配了。

      🍁 例外情况:
      有一个特殊的前缀匹配,即:^~,如果在前缀匹配结束后,命中的location是用 ^~ 修饰的,就不会进入正则匹配阶段了。

  3. 后置处理
    在uri匹配结束后,便执行命中location指令中,花括号{}内的指令。主要有两类,要么是从root指令配置的目录下查找相应的文件,要么执行其它代理类(见第①处)指令,整个流程与④处描述一致。

下图展示了整个location指令的执行流程
location指令执行流程

下表对各个匹配修饰符进行了整理对比,其中的优先级数字1、2、3,是数字越小,优先级越高

修饰符 优先级 是否为正则 区分大小写 命中后继续匹配 备注
2 ✔️ ✔️ 第一列没有内容,因为没有任何字符,其含义就是前缀匹配
= 1 ✔️
^~ 2 ✔️ ✔️ 在整个前缀匹配结束后,如果最终结果是一个^~修饰的location, 则不进行后续的正则匹配
~ 3 ✔️
~* 3 ✔️ ✔️

一个小优化

通常情况下,server里都会有一个location / {...} 这样的配置,它可以匹配所有的请求。如果一个网站的首页访问最频繁,比如http://localhsot/,但该网站却配置了非常多的location,那么首页uri这个请求,需要在匹配完所有的location后,才能得出最终命中的location为 / 。在此期间,其它的那些匹配尝试明显是多余的。为此,可能通过为 / 提供一个精确匹配来提高性能,就像下面这样:

location = / {
    ...
}
location / {
    ...
}

另外,像location = / {...} 这种精确匹配,内部再嵌套location指令就没有意义了。

几个特例

前缀匹配是区分大小写的,比如 location /books/ {...} 这个指令,请求/boOKs/red-rock.html就不匹配。但在一些大小写不敏感的操作系统上(如MacOS和Cygwin),0.7.7版本以后的nginx,则是能够匹配的。如果你打算验证一下这个大小写敏感的问题,建议不要通过浏览器方式,因为浏览器会将uri地址做规范化处理。建议通过命令行工具来验证,比如linux下的curl和wget。

0.7.40版本以后的nginx,其正则匹配中的捕获组是可以在其后的其它指令中引用的。

0.7.1 ~ 0.8.41这个版本范围内的nginx,有点特殊,只要命中一个前缀匹配就立即结束,不再匹配后续的前缀location,也不匹配其它正则location。因此,如果你使用的是这个范围内的nginx,在配置location时,就得精心按排好各个location在配置文件中的顺序,避免一个相似的,内容较短的location配置出现在较长者的前面。

文件路径排除URI中的匹配项

有时候,我们希望资源文件的路径中,不要出现URI中已匹配的部分,比如下面这个场景:

location /book/ {
    root /var/www/book;
}

请求地址http://localhost/book/ruby-on-rails-guide.pdf,对应到硬盘上的文件为/var/www/book/book/ruby-on-rails-guide.pdf。其中有两个连续的book目录,第一book来自root指令配置的根目录路径,第二个book来自请求uri。但我们希望这个路径上只有一个book目录,或者说文件路径中,不要包含uri中匹配的location部分,就像这样: /var/www/book/nginx-definitive-guide.pdf。用alias指令替换root可以达到这个目的,即像下面配置:

location /book/ {
    alias /var/www/book/;     # 将原来的root替换成alias
}

alias指令表明,在生成文件路径时,用alias指令后面的字符串,替换掉请求uri中与location匹配的部分,因此,该指令后面的内容,就可以看作是uri中匹配部分的别名。

如果location配置为正则匹配,根据alias的功能定义,它会替换掉整个请求的uri,也就是说,所有匹配此正则location的请求,都将返回相同的内容,即alias指令所配置的文件或目录。此时,

  • 如果alias后面的文本指向一个已存在的目录,则会引发重定向
  • 如果alias后面的文本是一个不存在的文件或目录,则会返回404

上述行为显然不是我们所期望的,它应该根据uri的不同,返回不同内容(web资源文件)。要达到此目的,需要在正则表达式中添加捕获组,并在alias指令中引用它们。示例如下:

location ~* ^/avatar/(.+\.(?:jpg|jpeg|gif|png))$ {
    alias /var/www/user/avatar/$1;
}

当访问http://localhost/avatar/jane.png时,将返回/var/wwww/user/avatar/jane.png文件。

上面这个示例中,alias指令仅仅是简单的引用了变量$1,并将其追加在末尾,它完全可以用root /var/www/user指令替代。下面这个示例更符合正则location与alias的组合:

location ~ "^/novel/(\d{4})/(\d{2})/\d{2})/(.+)-(.+)$" {
     alias /var/wwww/novel/$4/$1-$2-$3/$5.txt;
}

这是一个模拟小说文件的配置,$1、$2、$3、$4、$5分别代表年、月、日、作者、作品名,请求http://localhost/novel/2023/03/15/YuHua-KeepAlive,对应的响应文件路径为:/var/www/novel/YuHua/2023-03-15/KeepAlive.txt

示例二中的整个正则表达式写在了一对引号内,这是因为正则表达式中含有花括号 { 和 },它们都是nginx配置的关键字,需要做特殊处理。经验证,将正则表达式写在一对引号内可解决此问题,而采用对花括号字符使用反斜杠(即\{ 和 \})转义这种方式,是不行的。

命名location

除了模板location外,还支持一种叫做命名location的语法,即 location @name {...} 这种。顾名思义,命名location就是一个具有名称的location定义,这个location不匹配任何外部uri,它只能在nginx内部使用,通常与error_pagetry_files等指令一起使用。

比如下面这个配置(点击查看):
location /books/ {
    root /var/wwww/book-docs;
    index index.html;
    error_page 404 @fallback;   ☆
}

location @fallback {
    proxy_pass http://baby-home-server;
}

根据上面☆处的配置,如果出现了404错误,会内部重定向到名称为fallback的location中。就本示例而言,它最终会代理到baby-home-server(宝贝回家)这个Web服务上,通常是返回一个亲子寻找页面。

proxy_pass指令配置详解

proxy_pass指令主要用在location指令内,将匹配的请求转发到代理的服务上,这对于API类服务非常适用,再配合upstream指令的话,还可实现负载均衡。

  • 指令语法:proxy_pass uri
  • 适用范围:location指令内、limit_expect指令内、location指令内的if语句块

指令中的uri,协议部分支持http和https。主机部分,支持ip地址、nginx虚拟主机组unix socket。整个指令的执行分为两个阶段,如下:

  • 转发地址合成
    正如前面③处所述,proxy_pass指令新转发地址的合成逻辑,与它后面配置的内容是否是带有uri相关,如下:

    • 不带uri时(如http://localhost:8379)
      转发地址为:proxy_pass的配置内容 + 原始请求uri中去除掉协议、主机和端口后的剩余内容
    • 带有uri时(如:http://localhost:8379/http://localhost:8379/foo
      转发地址为:proxy_pass的配置内容 + 原始请求uri中去除掉协议、主机、端口和location配置内容后的剩余部分

    这里的uri,是指请求url中,端口与问号之间部分,如http://localhost:1983/user/list?start=36&minLevel=3中,uri内容为/user/list

    比如下面这个配置
    location /student/ {
        proxy_pass http://edu-server;                  # 配置⑴
    }
    
    location /honour/ {                                # 配置⑵
        proxy_pass http://edu-server/win-honours/;
    }
    
    location /score/ {                                 # 配置⑶
        proxy_pass http://edu-server/final-score;
    }
    

    下表列出了3个不同配置的转发uri合成情况

    请求 命中配置 转发地址 说明
    http://localhost/students/baseinfo/sophia.htm 配置⑴ http://edu-server/students/baseinfo/sophia.htm 该配置不带uri,代理地址只是简单地将proxy_pass与location配置拼接
    http://localhost/honour/list 配置⑵ http://edu-server/win-honours/list 该配置有uri部分,故代理地址中不保留location中匹配的部分(即/honour/)
    http://localhost/score/physical 配置⑶ http://edu-server/final-scorephysical 与第二个同理,只是最终的地址里,final-scorephysical这个串可能不符合实际需要,这里特意举这个例子,是为了说明,转发uri的合成,与配置末尾是否有斜杠无关
  • 执行转发
    在nginx内部,调用这个新的uri地址,然后将结果回传给最初的客户端。对于代理地址中的host部分,proxy_pass指令支持以下四种:

    • ip地址
      比如http://192.168.56.101/courses/list, 将直接转调到目标服务

    • 虚拟主机组
      将根据虚拟主机组的负载均衡策略,转发到具体的目标服务。比如http://edu-server/courses/list,其中edu-server是一个虚拟主机组,将根据edu-server内的服务配置情况进行转发

    • 域名
      如果一个域名,通过域名解析器后,得到了一组ip地址,则将以循环的方式调用这些ip。

    • UnixSocket
      nginx也支持unix socket方式的转发,但这在生产实践中,很少使用到,因为它是服务器内的进程间通信,尽管性能最好,但限制在了单个服务器内。另外,还需要其它一些配置,才能使unix socket真正工作起来

    当host不是一ip时,它有可能是一个主机组,也有可能是一个域名。proxy_pass指令的处理顺序为:先尝试查找主机组,如果没有找到,再尝试域名解析。

在合成转发地址时,有时候无法确定请求uri中,哪些部分应该被替换掉。此时,要么转发的地址与指令不带uri的情况相同,要么通过变量来指定,下面罗列出3种最常见的情况。

  1. location 为一个正则匹配,或 proxy_pass 指令位于命名 location 内
    这种情况下,proxy_pass的配置不允许指定 uri 部分。

    比如下面这个配置
    location ~ ^/novel/[^/]*mark-twain[^/]*/.+$ {
        proxy_pass http://novel-server;                  # ⑴
    }
    
    location @novel-authors {
        proxy_pass http://autor-server;                  # ⑵
    }
    

    与不带 uri 的 proxy_pass 指令一样,上面的配置,最终得到的转发url就是 proxy_pass 配置内容 + 请求uri

    上述配置里⑴和⑵处的proxy_pass指令,不可以包含uri信息,否则将导致配置文件解析报错。假如⑴处的配置为proxy_pass http://novel-server/;proxy_pass http://novel-server/novels/;,启动nginx时,将得到类似下面的错误:

    nginx: [emerg] "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block in /usr/local/nginx/conf/nginx.conf:89
    

    还可以通过nginx -t -c 配置文件路径命令来验证配置文件是否正确,避免直接重启nginx或重新加载配置文件

  2. location 中有 rewirte 指令,并且请求 uri 匹配 rewrite 的正则表达式
    这种情况下,原始请求的uri会被rewirte指令修改,修改后的uri,将直接传递给proxy_pass指令,而proxy_pass本身的uri(如果有的话)将被替换。示例如下:

    location /name/ {
        rewrite /name/([^/]+) /users?name=$1 break;
        proxy_passs http://user-center/main/basicinfo/;
    }
    

    假如请求url为 /name/jane-lotus, 该uri与rewrite的正则表达式是匹配的,因此,处理过程如下:

    • 原始url被rewrite指令修改为:/users?name=jane-lotus
    • 修改后的url传递给proxy_pass指令,新的转发地址为http://user-center/users?name=jane-lotus
      可以看到,proxy_pass中的uri部分,即/main/basicinfo/,被替换成了/user?name=jane-lotus

    如果请求uri为 /name/regions/shuangliu,由于它不匹配 rewrite 指令的正则表达式,因此,proxy_pass 的行为处于正常状态(即与“附带有uri”时的行为一致),最后得到的转发地址为http://user-center/main/basicinfo/regions/shuangliu,不包含location中匹配的/name/ 。

  3. proxy_pass 中使用了变量
    在使用了变量的情况下,将变量内容替换为真实文本后即为新的转发地址,示例如下:

    location /novel/ {
        proxy_passs http://book-server/books$request_uri;
    }
    

    当请求uri为/novel/three-body.html?page=3时,最终的转发地址为http://book-server/books/novel/three-body.html?page=3 。是直接替换掉proxy_passs中的$request_uri变量得来的。

小结

通过 location 指令,可实现根据不同请求 uri 进行不同配置的效果,因此它也更像是 http 请求路由。可分为精确匹配、前缀匹配和正则匹配。

proxy_pass 用于将一个 location 请求代理到另一个或另一组Web服务,在生成新的转发地址时,会根据 proxy_pass 是否带有uri而有所不同。

总的说来,location与proxy_pass,对其配置末尾的字符是否为斜杠,没有做特殊处理。


📣 转载说明
本文完全由作者原创,所有示例均经过测试,欢迎大家转载,但转载时,请务必说明出处,并做完整转载

posted @ 2023-03-22 21:48  顾志兵  阅读(8194)  评论(9编辑  收藏  举报