本文所使用的环境信息如下:
- windows11 (主机系统)
- virtual-box-7.0环境下的ubuntu-18.04
- nginx-1.22.1 (linux)
斜杠结尾之争
实践中,nginx里最常用的指令就是location和proxy_pass了。前者用于为不同请求uri指定不同nginx配置,后者用于匹配的location进行转发(通常是动态内容)。关于二者的配置,有一个老生常谈的话题,那便是:配置的值是否有斜杠结尾,对文件路径查找(或请求转发)行为有哪些影响?相关文章也非常多,且多数粗看一眼,照其行事,也能立即解决问题。鄙人私以为其中部分文章的说法是不严谨的,故特撰此文,以备己查。
结论
不再废话,直接上结论(如果对location和proxy_pass的功能和基本配置还不熟悉,建议先看后面的章节):
-
location
一般情况下,location指令不会对是否有斜杠结尾这个场景做特殊处理,除非满足以下条件:- location指令配置为前缀匹配
- 前缀的最后一个字符为斜杠 /
- 指令内嵌入了其它代理类指令
这些代理类指令有:proxy_pass、fastcgi_pass、uwsgi_pass、scgi_pass、memcached_pass、grpc_pass ①
满足以上条件后,也只会对一个特定的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地址尾部多了一个斜杠 / 😁
是的,你没看错,对locationsr的配置里,其最后一个字符为斜杠的特殊处理,就仅此而已了。用大白话讲就是:嘿,客户端伙计,你这家伙不讲武德呀,你请求的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命令会将请求代理到一个新的uri地址,这个新的代理地址,与proxy_passs配置的最后一个字符是否为斜杠 / 没有关系,只与proxy_pass的配置是否带有uri(这里的uri是指url中,端口之后与问号之前的部分)相关,具体如下 ③ :-
不带uri时(如http://localhost:8379)
新的地址构成为:proxy_pass的配置内容 + 原始请求URI中去除掉协议、主机和端口后的剩余内容 -
配置了uri时(如:http://localhost:8379/ 或 http://localhost:8379/foo )
新的地址构成为:proxy_pass的配置内容 + 原始请求uri中去除掉协议、主机、端口和location配置内容后的剩余部分
从上面可以看出,proxy_pass在创建新的转发地址时,总是会剔除掉原始uri中的协议、主机、端口。核心差异在于是否要去除掉location指令的配置内容。如果proxy_pass配置带有uri就去除,反之则不去除。
另外,像http://localhost:8379/这个地址很特别,因为去除掉协议、主机、端口后,就只剩下 / 了,这大概就是,以斜杠结尾的配置会去除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:带了一个最简短rui } 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的配置 |
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匹配、后置处理,详细说明如下:
-
uri规范化处理
这一步是处理原始uri串中不规范的内容,将其规范花后,方便后续做匹配,这些处理包括:- 解码 &xx 这样的url编码字符
- 解析 . 和 .. 到这些符号所引用的目录上,比如/comment/top/../top100,处理后会变成/comment/top100
- 将多个连续的/压缩成一个,比如/films/science-fiction///wandering-earth,处理后会变成/films/science-fiction/wandering-earth
-
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是用 ^~ 修饰的,就不会进入正则匹配阶段了。
-
-
后置处理
在uri匹配结束后,便执行命中location指令中,花括号{}内的指令。主要有两类,要么是从root指令配置的目录下查找相应的文件,要么执行其它代理类(见第①处)指令,整个流程与④处描述一致。
下图展示了整个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来自请求rui。但我们希望这个路径上只有一个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_page、try_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的配置内容 + 原始请求rui中去除掉协议、主机、端口和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的合成,与配置末尾是否有斜杠无关 - 不带uri时(如http://localhost:8379)
-
执行转发
在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种最常见的情况。
-
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或重新加载配置文件 -
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/ 。
-
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,对其配置末尾的字符是否为斜杠,没有做特殊处理。
📣 转发说明
本文完全由作者原创,所有示例均经过测试,欢迎大家转载,但转载时,请务必说明出处,并做完整转载