nginx中一个请求匹配到多个location时的优先级问题,马失前蹄了
背景
为什么讲这么小的一个问题呢?因为今天在进行系统上线的时候遇到了这个问题。
这次的上线动作还是比较大的,由于组织架构拆分,某个接入层服务需要在两个部门各自独立部署,以避免频繁的跨部门沟通,提升该接入层服务的变更效率。
该接入层服务之前是使用cookie + 内存session机制的,这次要独立部署,首先是将这种内存session机制改成分布式会话(使用redis),总之,就是做成无状态的。
再其次,就是将原来的流量网关nginx,升级成为openresty。openresty使用lua代码,判断请求应该分发到我们部门的接入层服务,还是另一个部门的接入层服务。
升级成openresty,这块涉及到两件事情,一个是openresty的安装,再一个是修改了原来的nginx.conf。今天升级后,大部分业务验证ok,唯独两三个业务报错,由于是app报错,所以是要了对方的用户名密码在我手机上登录,在电脑上charles抓包,发现报错的原因,竟然是个这。
处理过程
先给大家看下,nginx中原始的配置:
location ~ (^/(cgi-bin|servlet|chart)/|\.jsp$)
{
proxy_pass http://backendServer;
include proxy.conf;
}
这个location会匹配/servlet/json这样的请求,我们这次就是对这个请求做了改造,用lua判断应该反向代理到什么地方,如下:

我们最终的openresty(就是增强版本的ng,配置文件都是基本兼容的)配置大概如下:
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/json, http://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,我觉得正常:
location /Api/ 这个是之前就有的,本次没动
{
proxy_pass http://ApiGatewayServer/;
include proxy.conf;
}
但是,竟然也匹配上了下面这个location:
location ~ /servlet/json {
set $target_url '';
rewrite_by_lua_block {
...
proxy_pass http://$target_url;
}
}
此时,我才开始搜索,这个 location 后面的 ~ 符号的确切意思,我知道这个表示正则,但是没想到,这种请求路径/Api/servlet/json包含了/servlet/json也能匹配上。
我刚开始以为是这种匹配上了多个,那我是不是换下顺序就好了,把/Api那个location放到了文件最前面:
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开头的那些请求。
最终的改动如下:
// ^在正则中,一般表示匹配一行的开头,所以,我这里加了^
location ~ ^/servlet/json {
}
终于ok了。
说起来,确实是对原nginx的配置文件迁移出的这个问题,人家以前是:
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
语法如下:
Syntax: location [ = | ~ | ~* | ^~ ] uri { ... }
location @name { ... }
Default: —
Context: server, location
按文档的说法,location 和 uri之间,可以什么都没有,如我们上面的:
location /Api/
这种呢,就算是前缀匹配。
比如,
location / {
[ configuration B ]
}
/index.html 这种url就可以匹配。
当然,也可以在location和uri中间加如下几种符号:
=
完全匹配,比如,
location = / {
[ configuration A ]
}
只能匹配“/” 这个请求,其他请求都不能匹配,这个优先级最高
~ (uri部分为:大小写敏感的正则)或者 ~* (uri部分:大小写不敏感的正则)
这种就是正则匹配,也就是我们前面的
location ~ /servlet/json {
这种,居然就匹配上了
/Api/servlet/json,我不是很理解,但大家要谨慎。好好学习下这块的正则表达式怎么写。^~
这种,一会再讲。
匹配逻辑:
首先,对uri进行normalize,也就是,比如url有特殊字符的话,一般浏览器会自动编码成%XX这种,另外,可能url中也有相对路径,或者有重复的斜杠,都要处理。
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,在我们前面的例子中,就会找到:
location /Api/
但是,找到了之后,不会结束;还要继续找正则类型的location,这次就是按照文件的顺序,从上到下找,
如果找到了,直接使用;如果没找到,则使用最长前缀的那个。
这次,在我们的例子中,就会找到如下部分,且直接使用这个location,不再继续找。
location ~ /servlet/json {
set $target_url '';
rewrite_by_lua_block {
...
proxy_pass http://$target_url;
}
}
所以,这里的逻辑就是:
1、先找 = 这种完全匹配的,找到就结束;
2、开始找前缀匹配这种的,没找到就算了,找到了也只是做个标记,把最长前缀那个location记录下来,假设为location A;
3、开始找正则这种类型的location,从上到下找,找到就直接停止,就用这个;没找到就继续找下一个正则类型;如果最终,完全没找到正则类型的,就用第二步里找到的location A
当然了,对于这个机制,有个小例外,就是有一种符号,可以打破这种机制:
^~
这个符号加在前缀类型的location上,如果最长前缀的那个location,加了这个符号,直接结束,不找正则类型的了。原文:
If the longest matching prefix location has the “^~” modifier then regular expressions are not checked.
这个符号,感觉很容易误用,一开始没研究之前,我以为^是一行的开头的意思,危险。
我以前,以为前缀这种优先级很高,没想到,比正则要低,被正则压着打啊。
推荐工具
location ~ /servlet/json {
proxy_pass http://localhost:8082;
}
location /Api/
{
proxy_pass http://localhost:8083;
}
这边有个网站,可以贴配置进去,检测到底匹配上哪个:

参考资料
大家也可以看下参考文档:
https://stackoverflow.com/questions/5238377/nginx-location-priority


nginx中一个请求匹配到多个location时的优先级问题,马失前蹄了的更多相关文章
- Nginx如何处理一个请求
看了下nginx的官方文档,其中nginx如何处理一个请求讲解的很好,现在贴出来分享下.Nginx首先选定由哪一个虚拟主机来处理请求.让我们从一个简单的配置(其中全部3个虚拟主机都在端口*:80上监听 ...
- Nginx中的一些匹配顺序
Nginx中经常需要做各种配置,总结如下: 1.server_name配置 nginx中的server_name指令主要用于配置基于名称虚拟主机,同一个Nginx虚拟主机中,可以绑定多个server_ ...
- [转载]Nginx如何处理一个请求
http://nginx.org/cn/docs/http/request_processing.html 对我的扫盲文章 基于名字的虚拟主机 Nginx首先选定由哪一个虚拟主机来处理请求.让我们从一 ...
- nginx学习笔记(7)Nginx如何处理一个请求---转载
如何防止处理未定义主机名的请求基于域名和IP混合的虚拟主机一个简单PHP站点配置 基于名字的虚拟主机 Nginx首先选定由哪一个虚拟主机来处理请求.让我们从一个简单的配置(其中全部3个虚拟主机都在端口 ...
- Nginx 如何处理一个请求
基于名字的虚拟主机 Nginx首先选定由哪一个虚拟主机来处理请求.让我们从一个简单的配置(其中全部3个虚拟主机都在端口*:80上监听)开始: server { listen 80; server_na ...
- Django2.0中的urlpattern匹配不输入任何网址时的写法
如果使用urlpattern匹配不输入任何网址时,应该如何写? 例如:仅匹配http://127.0.0.1:8000/时想要跳转到某个页面,这时urlpattern中的url规则应该写成: 情况1: ...
- nginx中server的匹配顺序
在开始处理一个http请求时,nginx会取出header头中的host,与nginx.conf中每个server的server_name进行匹配,以此决定到底由哪一个server块来处理这个请求. ...
- nginx中location详解
Location block 的基本语法形式是: location [=|~|~*|^~|@] pattern { ... } [=|~|~*|^~|@] 被称作 location modifier ...
- Nginx中虚拟主机配置
一.Nginx中虚拟主机配置 1.基于域名的虚拟主机配置 1.修改宿主机的hosts文件(系统盘/windows/system32/driver/etc/HOSTS) linux : vim /etc ...
- Nginx中root与alias的用法及区别:
Nginx中root与alias都是定义location {}块中虚拟目录访问的文件位置: 先看看两者在用法上的区别: location /img/ { alias /var/www/image/; ...
随机推荐
- Intellij IDEA 插件开发
写在前面 很多idea插件文档更多的是介绍如何创建一个简单的idea插件,本篇文章从开发环境.demo.生态组件.添加依赖包.源码解读.网络请求.渲染数据.页面交互等方面介绍,是一篇能够满足基本的插件 ...
- Kafka 如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性?
如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性? >幂等性,通俗点说,就一个数据,或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的,不能出错. kafka 的机制: K ...
- Logistic Regression and its Maximum Likelihood Estimation
从 Linear Regression 到 Logistic Regression 给定二维样本数据集 \(D = \left\{ (\vec{x}_{1}, y_{1}), (\vec{x}_{2} ...
- Java扩展Nginx之七:共享内存
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 作为<Java扩展Nginx>系 ...
- modulemap的使用方法
modulemap的作用 modulemap 文件是用来解决 C,Object-C,C++ 代码在 Swift 项目中集成的问题的. 在 Swift 项目中,如果需要使用 C,Object-C 或 ...
- 行行AI人才直播第11期:墨尔本大学数据科学高级讲师-宫明明《机器学习:从统计到因果,人工智能的发展之路》
行行AI人才是博客园和顺顺智慧共同运营的AI行业人才全生命周期服务平台. 马克斯·普朗克智能系统中心主任曾在国际数学家大会进行了题为 From Statistical to Causal Learni ...
- Easygraph:全面高效的图分析与社会计算开源工具
前言图是对事物之间关系的一种原生的表达,利用图可以深入直接地认识世界中的关联.社交网络.交易数据.知识图谱.交通运输.生物技术等都是图数据的典型应用.社交网络是一种特殊的图数据,它建立在图网络的基础上 ...
- ls 和 du显示文件大小不一样
查看当前文件系统的磁盘使用 df -k / Filesystem 1K-blocks Used Available Use% Mounted on /dev/nvme0n1p2 97844508 37 ...
- 2021-6-16 TcpIp
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- 日历插件zaneDate 不依赖任何第三方插件 简单高效
先来找图看看时间选择器的效果: 没错就是这个吊样,如果你不需要这个色调,你可以fork我的github项目任意修改美美的色调. 当然也欢迎你给我提很多很多的bug让我改不停 . ...