Nginx的location的规则深入学习和记录
之所以写这篇博客,是因为之前配置Nginx的location遇到了各种问题,不甚其解,实在是感觉越配置越乱,开始以为自己会了,后来才发觉自己的认知其实是错误的。这篇并非什么解读源码之类的分析(还没到那水平),主要是对于我遇到的问题进行一些测试进行的总结,可能无法涵盖所有的情况,不恰当的地方还望指正。
表达式类型
-
= :进行普通字符精确匹配。也就是完全匹配。
-
~ :表示执行一个正则匹配,区分大小写
-
~* :表示执行一个正则匹配,不区分大小写
-
^~:表示普通字符匹配,使用前缀匹配;如果匹配成功,则不再匹配其他location。
-
@ :它定义一个命名的 location,使用在内部定向时。
优先级说明
nginx的location顺序没有太大关系,与表达式的类型有关。基本的规则是:相同类型的表达式,字符串长的会优先匹配。
按优先级排列说明:
- 等号类型(=)的优先级最高;一旦匹配成功不会再去查找其他匹配项。
- ^~类型表达式,特别注意这种知识普通字符匹配,并非正则匹配,而且如果它一旦匹配成功,则不再查找其他匹配项,即使后面匹配的字符串更长。
- 正则表达式类型(~ 或者~*)的优先级次之。如果有多个location的正则能匹配的话,则使用正则表达式最长的那个location。
- 常规字符串匹配类型,最低的优先级,匹配的规则:按前缀匹配。
测试的示例
基本的测试环境,访问的测试地址:http://127.0.0.1/docs/introduction.html,代理访问:http://127.0.0.1:8081/docs/introduction.html;Nginx监听80
端口,上游服务器Tomcat的端口为8081
。
情况1,下列正则匹配的location,都是不合法的案例
相同点都是proxy_pass
后面的上游地址在ip和port后面带有uri
,正则匹配的后面是不可以有uri的,只能是端口结尾,不过后面可以带正则匹配的变量:
情况2,正则匹配的location,成功的案例
proxy_pass
后面的上游地址在ip和port后面没有uri,也就是正则的proxy_pass
的上游服务器地址没有配置uri。那么匹配正则表达式的location
后的整个uri会作为上游服务器的请求的uri。例如下列的情况,http://127.0.0.1/docs/introduction.html
,docs能匹配上,所以整个uri:/docs/introduction.html
都会拼接到proxy_pass的url的后面。
特别注意:proxy_pass
最后面不能有/
,因为以/
结尾,则匹配的location部分会被截取掉然后截取后的uri拼接到proxy_pass
的/
后面。因为location是正则表达式,导致location的前半部分可能错在多种情况,
例如location ~ docs
,如果允许正则的location的proxy_pass
以/
,那么按照以/
结尾的proxy_pass
的规则,访问的时候:http://127.0.0.1/docs和http://127.0.0.1/testdocs
等等url都可以匹配上http://127.0.0.1/
这个地址,在同一个location中不能存在多个访问的url地址匹配了同一个上游服务器的地址,故此正则匹配的location,是不可以配置proxy_pass的上游服务器地址为/
结尾的。
下列的正则也能匹配到
如果非正则,如下列写法必然会出错:
情况3:常规字符串匹配,正常访问
a.正常访问
可以正常访问,其实通过:http://127.0.0.1/docsintroduction.html
,也可以访问的,但是可能部分其他资源,例如css和图片的加载会出现问题。因为浏览器保存的baseUrl
会变成了http://127.0.0.1/
,举个例子:有个资源文件为index.css
,那么在加载introduction.htm
l时,会需要加载它,但是应为浏览器的baseUrl
的问题,导致请求服务器时地址变为了:http://127.0.0.1/index.css,那么可以对应一下下列的配置,显然是没有匹配到/docs
的,所以index.css
会404,页面渲染出现问题。所以正常一般都是这样访问:http://127.0.0.1/docs/introduction.html
,那么baseUrl会保持为:http://127.0.0.1/docs/
,就没有问题了
b.正常访问
c.正常访问
d.正常访问(同时验证网上某篇文章所描述的第四种情况)
网上的例子,https://blog.51cto.com/chenwenming/1203537,按第四种来说应该会访问到了http://127.0.0.1/docsintroduction.html
,显然不是。这篇文章中的第四种情况是错的,因为以http://127.0.0.1/docs/introduction.html
访问是完全没问题。
情况4:访问失败
访问http://127.0.0.1/docs/introduction.html
失败,如果要成功访问,则访问地址应该为:http://127.0.0.1/docsdocs/introduction.html
因为/docsdocs
的/docs
会匹配上,然后后面的docs/introduction.html
则直接回拼接到http://127.0.0.1:8081/
的后面。
情况5:访问失败
访问http://127.0.0.1/docs/introduction.html
失败,如果要成功访问,则访问地址应该为:http://127.0.0.1/docs/docs/introduction.html
。原因与情况4一致。
对于情况4和情况5,共同点都是roxy_pass
的上游服务器地址以”/”
结束,所以对于情况4中,在浏览器中的输入方位地址为:http://127.0.0.1/docs 等于 http://127.0.0.1/ ,所以访问http://127.0.0.1/docs/introduction.html 则访问上游服务器地址为:http://127.0.0.1/introduction.html 所以访问失败。对于情况5也是同样的原因。proxy_pass的上游服务器地址以”/”
结束,则请求的uri需要与locaiton中的匹配成功后,还需减去前面匹配的部分,将后面剩余的拼接到proxy_pass的上游服务器url的后面。
规律总结
这些规律总结,主要是基于上面的测试,还有一些是自己实践中的总结。
1.正则匹配的location
,proxy_pass
的上有服务器的地址中不能以/
结尾。
2.正则匹配的location
的整个url是可以直接拼接到proxy_pass最后的
3.关于location中
的字符串是否是以/结尾:
- a.不以
/
结尾,例如:location /docs
- b.以
/
结尾,例如:location /docs/
两者本质没什么区别,都是普通的常规字符串匹配类型,以/docstest和/docs/test为例,/docstest
则location /docs
可以匹配,location /docs/
无法匹配,而/docs/test
则两者都可以匹配。
4.关于proxy_pass
的上游服务器是否以/结尾
- a.对于不是以/结尾的
proxy_pass
,例如:proxy_pass http://127.0.0.1:8081/docs
- b.对于以/结尾的
proxy_pass
,例如:proxy_pass http://127.0.0.1:8081/docs/
区别是不带/
结尾的,则拼接在http://127.0.0.1:8081/docs
后面存在两种可能,一种可能是/xxx
,另一种是xxx
,没有/
;而对于以/
结尾,则表示拼接在 http://127.0.0.1:8081/docs/
后面只可能是xxx
,即使请求的url与location匹配完后的后半部分是/xxx
,则也只会取xxx
,因为 http://127.0.0.1:8081/docs/
后已经有/
。
#不以/结尾
location /docs {
proxy_pass http://127.0.0.1:8081/docs;
}
#以/结尾
location /docs {
proxy_pass http://127.0.0.1:8081/docs/;
}
请求的URL(浏览器) | 不以/结尾(Nginx转发) | 以/结尾(Nginx转发) |
---|---|---|
http://127.0.0.1/docs/introduction.html | http://127.0.0.1:8081/docs/introduction.html | http://127.0.0.1:8081/docs/introduction.html 【1】 |
http://127.0.0.1/docsintroduction.html | http://127.0.0.1:8081/docsintroduction.html 【2】 | http://127.0.0.1:8081/docs/introduction.html [3] |
针对表格中标注的地方补充说明:
【1】:对于该请求,按照规则,访问http://127.0.0.1/docs/introduction.html
,在与/docs
匹配后,剩下的后半部分为/introduction.html
,如果直接拼接到已经带/
结尾的proxy_pass,显然是要有问题,而以/
结尾的proxy_pass,对这种的剩下的后半部分/introduction.html
处理是不需要带/
,而是将introduction.html
拼接在最后,向上游发起请求。
【2】:404错误,原因是转发给上游服务器成了http://127.0.0.1:8081/docsintroduction.html
。
【3】:能拿到这个页面数据,但是其他资源拿不到,因为这个baseUrl是http://127.0.0.1/了,不再是http://127.0.0.1/docs,举个例子,此时页面获取的index.css,会以http://127.0.0.1/index.css发起,显然没法找到能匹配的nginx的location,所以其他资源都是404,其实也算是不正常的,这里用这两个为了能解释带/和不带的区别。
5.整个location只有个/
这种其实没啥好解释的…,直接可以把它理解为一个默认拦截,任何未被拦截的请求(异常除外),都交给它处理。
location / {
proxy_pass http://127.0.0.1:8081/docs;
}