• nginx中一个请求匹配到多个location时的优先级问题,马失前蹄了


    背景#

    为什么讲这么小的一个问题呢?因为今天在进行系统上线的时候遇到了这个问题。

    这次的上线动作还是比较大的,由于组织架构拆分,某个接入层服务需要在两个部门各自独立部署,以避免频繁的跨部门沟通,提升该接入层服务的变更效率。

    该接入层服务之前是使用cookie + 内存session机制的,这次要独立部署,首先是将这种内存session机制改成分布式会话(使用redis),总之,就是做成无状态的。

    再其次,就是将原来的流量网关nginx,升级成为openresty。openresty使用lua代码,判断请求应该分发到我们部门的接入层服务,还是另一个部门的接入层服务。

    升级成openresty,这块涉及到两件事情,一个是openresty的安装,再一个是修改了原来的nginx.conf。今天升级后,大部分业务验证ok,唯独两三个业务报错,由于是app报错,所以是要了对方的用户名密码在我手机上登录,在电脑上charles抓包,发现报错的原因,竟然是个这。

    处理过程#

    先给大家看下,nginx中原始的配置:

    Copy
    location ~ (^/(cgi-bin|servlet|chart)/|\.jsp$) { proxy_pass http://backendServer; include proxy.conf; }

    这个location会匹配/servlet/json这样的请求,我们这次就是对这个请求做了改造,用lua判断应该反向代理到什么地方,如下:

    image-20231014164016112

    我们最终的openresty(就是增强版本的ng,配置文件都是基本兼容的)配置大概如下:

    Copy
    location ~ /servlet/json { set $target_url ''; rewrite_by_lua_block { ... proxy_pass http://$target_url; } } location /Api/ 这个是之前就有的,本次没动 { proxy_pass http://ApiGatewayServer/; include proxy.conf; } location ~ (^/(cgi-bin|chart)/|\.jsp$) { proxy_pass http://info-tech-department-upstream; include proxy.conf; }

    在我们上述这个配置里,http://www.test.com/servlet/json 这样的请求,就会匹配上location /servlet/jsonhttp://www.test.com/Api 这样的请求,就会匹配上location /Api,但是,我抓包后,发现竟然报错的请求长这样:

    http://www.test.com/Api/servlet/json

    而查看openresty日志,发现其匹配上了location /servlet/json,而不是预期的location /Api/,这自然就是问题所在了。

    http://www.test.com/Api/servlet/json 这样一个请求,能匹配上下面这个location,我觉得正常:

    Copy
    location /Api/ 这个是之前就有的,本次没动 { proxy_pass http://ApiGatewayServer/; include proxy.conf; }

    但是,竟然也匹配上了下面这个location:

    Copy
    location ~ /servlet/json { set $target_url ''; rewrite_by_lua_block { ... proxy_pass http://$target_url; } }

    此时,我才开始搜索,这个 location 后面的 ~ 符号的确切意思,我知道这个表示正则,但是没想到,这种请求路径/Api/servlet/json包含了/servlet/json也能匹配上。

    我刚开始以为是这种匹配上了多个,那我是不是换下顺序就好了,把/Api那个location放到了文件最前面:

    Copy
    location /Api/ 这个是之前就有的,本次没动 { proxy_pass http://ApiGatewayServer/; include proxy.conf; } location ~ /servlet/json { set $target_url ''; rewrite_by_lua_block { ... proxy_pass http://$target_url; } }

    结果还是没效果。

    没效果的话,我最终解决的办法就是,修改location ~ /servlet/json为只匹配/servlet/json开头的那些请求。

    最终的改动如下:

    Copy
    // ^在正则中,一般表示匹配一行的开头,所以,我这里加了^ location ~ ^/servlet/json { }

    终于ok了。

    说起来,确实是对原nginx的配置文件迁移出的这个问题,人家以前是:

    Copy
    location ~ (^/(cgi-bin|servlet|chart)/|\.jsp$) { proxy_pass http://backendServer; include proxy.conf; }

    这个是匹配/cgi-bin、/servlet、/chart开头的请求,或者是jsp结尾的请求,我一迁移,就把意思整错了。

    那这块的匹配机制到底是怎样的呢?

    location匹配机制#

    官网:

    https://nginx.org/en/docs/http/ngx_http_core_module.html#location

    语法如下:

    Copy
    Syntax: location [ = | ~ | ~* | ^~ ] uri { ... } location @name { ... } Default: — Context: server, location

    按文档的说法,location 和 uri之间,可以什么都没有,如我们上面的:

    Copy
    location /Api/

    这种呢,就算是前缀匹配。

    比如,

    Copy
    location / { [ configuration B ] }

    /index.html 这种url就可以匹配。

    当然,也可以在location和uri中间加如下几种符号:

    • =

      完全匹配,比如,

      Copy
      location = / { [ configuration A ] }

    ​ 只能匹配“/” 这个请求,其他请求都不能匹配,这个优先级最高

    • ~ (uri部分为:大小写敏感的正则)或者 ~* (uri部分:大小写不敏感的正则)

      这种就是正则匹配,也就是我们前面的

      Copy
      location ~ /servlet/json {

      这种,居然就匹配上了/Api/servlet/json,我不是很理解,但大家要谨慎。好好学习下这块的正则表达式怎么写。

    • ^~

      这种,一会再讲。

    匹配逻辑:

    首先,对uri进行normalize,也就是,比如url有特殊字符的话,一般浏览器会自动编码成%XX这种,另外,可能url中也有相对路径,或者有重复的斜杠,都要处理。

    Copy
    The matching is performed against a normalized URI, after decoding the text encoded in the “%XX” form, resolving references to relative path components “.” and “..”, and possible compression of two or more adjacent slashes into a single slash.

    接下来,nginx首先会找出整个server块中,前缀匹配的所有location(就是location和uri中间啥都不加的那种),然后挨个匹配,找出最长前缀匹配的那个location,在我们前面的例子中,就会找到:

    Copy
    location /Api/

    但是,找到了之后,不会结束;还要继续找正则类型的location,这次就是按照文件的顺序,从上到下找,

    如果找到了,直接使用;如果没找到,则使用最长前缀的那个。

    这次,在我们的例子中,就会找到如下部分,且直接使用这个location,不再继续找。

    Copy
    location ~ /servlet/json { set $target_url ''; rewrite_by_lua_block { ... proxy_pass http://$target_url; } }

    所以,这里的逻辑就是:

    Copy
    1、先找 = 这种完全匹配的,找到就结束; 2、开始找前缀匹配这种的,没找到就算了,找到了也只是做个标记,把最长前缀那个location记录下来,假设为location A; 3、开始找正则这种类型的location,从上到下找,找到就直接停止,就用这个;没找到就继续找下一个正则类型;如果最终,完全没找到正则类型的,就用第二步里找到的location A

    当然了,对于这个机制,有个小例外,就是有一种符号,可以打破这种机制:

    Copy
    ^~

    这个符号加在前缀类型的location上,如果最长前缀的那个location,加了这个符号,直接结束,不找正则类型的了。原文:

    Copy
    If the longest matching prefix location has the “^~” modifier then regular expressions are not checked.

    这个符号,感觉很容易误用,一开始没研究之前,我以为^是一行的开头的意思,危险。

    我以前,以为前缀这种优先级很高,没想到,比正则要低,被正则压着打啊。

    推荐工具#

    https://nginx.viraptor.info/

    Copy
    location ~ /servlet/json { proxy_pass http://localhost:8082; } location /Api/ { proxy_pass http://localhost:8083; }

    这边有个网站,可以贴配置进去,检测到底匹配上哪个:

    image-20231014173853294

    参考资料#

    大家也可以看下参考文档:

    https://stackoverflow.com/questions/5238377/nginx-location-priority

    image-20231014173452342

    image-20231014173721448




  • 相关阅读:
    2403C++,C++20协程通道
    如何做专利挖掘,关键是寻找专利点,其实并不太难
    Gauva的ListenableFuture
    iOS自动化测试方案(三):WDA+iOS自动化测试解决方案
    面试:Android中的一些小问题随笔
    二、Eureka服务注册与发现
    网课答案公众号搭建-——网课题库接口
    选座位吧(10)
    22-06-24 linux(01) linux环境搭建和命令行
    Web安全漏洞解决方案
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/17764465.html