存档

文章标签 ‘location’

一文彻底读懂nginx中的location指令

2020年3月6日 没有评论

location指令是nginx中最关键的指令之一,location指令的功能是用来匹配不同的url请求,进而对请求做不同的处理和响应,这其中较难理解的是多个location的匹配顺序,本文会作为重点来解释和说明。

开始之前先明确一些约定,我们输入的网址叫做请求URI,nginx用请求URI与location中配置的URI做匹配。

location格式

location有两种格式:

  • 匹配uri类型,有四种参数可选,当然也可以不带参数。
  • 命名location,用@来标识,类似于定义goto语句块。

location [ = | ~ | ~* | ^~ ] uri { ... }
location @name { ... }02

location匹配参数解释

参数
解释

location后没有参数直接跟着URI,表示前缀匹配,代表跟请求中的URI从头开始匹配。
~执行一个正则匹配,区分大小写。
~*执行一个正则匹配,不区分大小写。
^~普通字符匹配,多用来匹配目录。
=执行普通字符精确匹配。
@"@" 定义一个命名的 location,@定义的locaiton名字一般用在内部定向,例如error_page, try_files命令中。它的功能类似于编程中的goto。

location匹配顺序

nginx有两层指令来匹配请求URI。第一个层次是server指令,它通过域名、ip和端口来做第一层级匹配,当找到匹配的server后就进入此server的location匹配。location的匹配并不完全按照它们在配置文件中出现的顺序来匹配,请求URI会按如下规则跟server里配置的location匹配。

  1. 寻找有没有“=”等号参数完全匹配的location,如果有完全匹配的等号location则停止匹配,执行该location中的指令,不去匹配其它类型的location。
  2. 匹配所有非正则表达式URI的location(包括空,=,^~三种参数)。找到请求URI和location URI按前缀匹配最长的location,如果这个最长的location的参数是^~,则停止匹配,执行该location中的指令,否则暂存该location。
  3. 匹配正则表达式URI的location(包括~,~*两种参数),按location在配置文件中出现的顺序匹配,如果找到第一个匹配的locaiton则停止匹配,执行该location。
  4. 匹配完所有正则表达式都没有匹配的location,则执行第二步中暂存的最长前缀匹配location。

简单来说按这个规则:
= > ~^ > ~ = ~* >最长前缀匹配 > /

匹配问号后的参数

请求URI中问号后面的参数是不能在location中匹配到的,这些参数存储在$query_string变量中,可以用if来判断。

例如,对于参数中带有单引号'进行匹配然后重定向到错误页面。

/plus/list.php?tid=19&mid=1124'

if ( $query_string ~* ".*[;'<>].*" ){
return 404;
}

location URI结尾带不带/

这个很多解释不太准确,我有必要多说几句。

对于请求URI结尾是否带有/,一般的处理逻辑是带/表示访问目录,不带/表示访问文件,如果文件不存在也会去匹配目录。例如访问http://www.nginx.cn/images/和http://www.nginx.cn/images,前面的请求会匹配目录,后面的请求会先匹配文件,文件不存再匹配目录

对于locatioin中的URI来说,如果URI的结尾带有/,并且location要执行的命令式是proxy_pass、fastcgi_pass、uwsgi_pass、scgi_pass、memcached_pass、grpc_pass之一。例如:

location  /images/ {
proxy_pass http://www.redis.com.cn
}

对于这种情况,nginx会做特殊处理,不管images命名的文件或目录存在不在,如果你访问http://www.nginx.cn/images会被重定向到http://www.nginx.cn/images/。

所以如果你想这两种请求对应不同的处理,就要明确增加不带/结尾的location配置。

location  /images {
proxy_pass http://www.rabbitmq.cn
}
location /images/ {
proxy_pass http://www.redis.com.cn
}

命名location

带有"@"的location是用来定义一个命名的location,这种location不参与请求匹配,一般用在内部定向。例如用在error_page, try_files命令中。它的功能类似于编程中的goto。

location  /images {
try_files $uri $uri/ @name;
}
location @name {
...
}

例子

location  = / {
  # 只匹配请求 "/"
  [ configuration A ] 
}
location  / {
  # 匹配任何请求,因为所有请求都是以"/"开始
  # 但是更长字符匹配或者正则表达式匹配会优先匹配
  [ configuration B ] 
}
location /documents/ {
  # 匹配所有 /documents/ 开头的请求,在没有正则表达
  # 式匹配时选择该locaiton
  [ configuration C ]
}
location ^~ /images/ {
  # 匹配任何以 /images/ 开始的请求,并停止匹配其它location
  [ configuration D ] 
}E
location ~* .(gif|jpg|jpeg)$ {
  # 匹配以 gif, jpg, or jpeg结尾的请求. 
  # 但是所有 /images/ 目录的请求将由 [Configuration D]处理.   
  [ configuration E ] 
}

请求URI例子:

  1. / -> 匹配A
  2. /index.html -> 匹配B
  3. /documents/a.html -> 匹配C
  4. /images/1.gif -> 匹配D
  5. /documents/1.jpg -> 匹配E

不知道有没有解释不清楚的地方,希望没把大家带沟里去。

留言告诉我吧。

分类: nginx 标签:

nginx location 配置踩坑过程分享

2015年2月6日 3 条评论

问题描述
我们的业务系统比较复杂,但最终提供给用户的访问接口比较单一,都是使用 Nginx 来做一个代理转发,而这个代理转发,往往需要匹配很多种不同类型的 URL 转给不同的服务。这就使得我们的 Nginx 配置文件变得很复杂,粗略估计了下,我们有近20个 upstream,有近60个 location 匹配。这些配置按照模块分布在不同的文件中,虽然复杂,但是仍然在我们的努力下运行的良好。直到有一天,有位同事给我反映说偶尔有些 URL 会出现 404 的问题。一开始没太在意,因为他也说不准是哪一种 URL 才遇到这个问题。

问题查找
后来,慢慢的查找,找到了一些规律,一开始只知道是 tomcat 那边返回 404了,想到 Nginx 都代理给了 tomcat,一开始就怀疑是程序的问题,不会想到是 Nginx。

我开始查找代码的问题,我在本地的开发环境,尝试了很久,我使用 8080 端口访问,不论如何都是正确的结果,可是生产环境就是不行。然后我就听信了某坑友同事的理论,重启解决 95% 的问题,重装解决 100%的问题,我尝试重启了 tomcat 和 Nginx,依然不行,然后是重装,你猜结果如何????? ——想啥呢?当然也是不行!

后来就开始怀疑是生产环境和开发环境的差异,去服务器上访问 8080 端口,仍然是可以的。可是一经过 Nginx 代理,就不行。这个时候才开始怀疑是 Nginx 出了什么问题。

Nginx 怎么会出问题呢,业务系统中 URL 模式 /helloworld/* ,这样的 URL 我们都是统一处理的。怎么会出现一些行,一些不行呢。问题表现为 A URL (/helloworld/nn/hello/world)没问题,而 B URL(/helloworld/ii/hello/world) 有问题。

所以到目前为止,基本可以肯定是 Nginx 的 location 上出了一些问题。

问题解决
因篇幅有限,为了直面本次问题的核心,我不再贴出完整的 Nginx 配置,我简化此次问题的模型。请看如下 Nginx 配置,这是我们之前的会导致问题的错误配置模型。

注意,这里有几点需要说明一下,生产环境的 Nginx 服务器配置文件比这里要复杂很多,而且是按模块分布在不同的文件中的。这里简化模型后,使用 Http 响应状态码 60x 来区分到底被哪个 location 匹配到了。
我针对当时的情况,做了大量尝试,最终的简化版本如下:

尝试1:http://localhost/helloworld ==> 602 符合预期
尝试2:http://localhost/helloworld/hello ==> 603 符合预期
尝试3:http://localhost/ii ==> 604 符合预期
尝试4:http://localhost/ii/oo ==> 604 符合预期
尝试5:http://localhost/ii/pp/kk ==> 605 符合预期
尝试6:http://localhost/ii/pp/kk/ll ==> 605 符合预期
尝试7:http://localhost/helloworld/scripts/aaa.js ==> 606 符合预期
尝试8:http://localhost/helloworld/ii/hello/world ==> 605 不符合预期,预期为【603】
上面这些尝试支持读者自行试验,Nginx 配置文件是完整可用的,我本地 Nginx 的版本是1.6.2

问题就在这里:我这里是事后,把这些匹配 location 标记成了不同的响应码,才方便查找问题。当发现这个不符合预期后,我还是难以理解,为何我一个以 /helloworld 开头的 URL 会被匹配到 605 这个以 /ii 开头的 location 里面来。在当时的生产环境中,以 /ii 的配置统一放在另外一个文件中,这里是很难直观的察觉出来这个 /ii 跟访问的 URL 里面的 /ii 的关系。

我不得不重新编译了 Nginx ,加上了调试参数,修改配置项,看调试日志了。

这里不再讲如何给 Nginx 加调试的编译参数,可自行查看相关文档。修改配置项很简单,只需要在

error_log logs/error.log;
后面加上 debug 就可以了。

打出详细调试日志后,访问
http://localhost/helloworld/ii/hello/world
我得到了这样的一段日志(省略掉了前后无用的日志,只保留有意义的一段):

可以看到,Nginx 测试了几次 location 匹配,最终选择了
~ "/ii/[^/]+/[^/]+"
这个作为最终的匹配项。到这里问题就完全展现出来了,我们本来的意思,是要以 /ii 开头,后面有两个或者更多的 / 分割的 URL 模型才匹配,但是这里的正则表达式匹配写的不够精准,导致了匹配错误。正则表达式没有限制必须从开头匹配,所以才会匹配到 /helloworld/ii/hello/world 这样的 URL 。

解决办法就是在这个正则表达式前面加上 ^ 来强制 URL 必须以 /ii 开头才能匹配.

/ii/[^/]+/[^/]+
变成
^/ii/[^/]+/[^/]+
至此,这个坑被填上了,消耗的是五个小时和一个字符。

相信很多人在写 Nginx 的location 的时候都会 location ~ /xxx 或者 location /iii 这样简单了事,但是我想说的是能尽量精确就尽量精确,否则出现问题的时候,非常难以查找。

有关 Nginx 的 location 匹配规则,可以查看: http://nginx.org/en/docs/http/ngx_http_core_module.html

问题总结
这个问题看似简单,却也隐含了不少问题,值得我们深思。

计算机或者软件出的问题往往是确定的,你发现他捉摸不定的时候,往往是没有观察到问题点
追踪一个问题,如果有一个必现方式,一定要紧追不舍,这就是所谓线索
当你实在是找不到问题所在的时候,要怀疑一下之前被自己排除掉的可能性
借助各个组件的详细调试日志来查找问题,往往能得到意想不到的效果
程序员的价值不是用行数,字数,提交数衡量的!

原文地址http://blog.coding.net/blog/tips-in-configuring-Nginx-location

分类: nginx 标签:

nginx location匹配规则

2012年8月29日 31 条评论

location匹配命令

~      #波浪线表示执行一个正则匹配,区分大小写
~*    #表示执行一个正则匹配,不区分大小写
^~    #^~表示普通字符匹配,如果该选项匹配,只匹配该选项,不匹配别的选项,一般用来匹配目录
=      #进行普通字符精确匹配
@     #"@" 定义一个命名的 location,使用在内部定向时,例如 error_page, try_files

阅读全文...

分类: nginx 标签: , ,