Nginx Location匹配原理及源码分析

21 篇文章 2 订阅

一 Location的作用

上篇我们对Nginx如何对请求进行server匹配进行了分析。在Nginx定义的每一个server中,还可以定义多个不同的location。这些不同的location根据请求中的URI的不同对HTTP请求进一步细分,提供不同的处理方法和服务。比如,根据请求中的URI不同,对于有些服务Nginx可以通过提供本地静态文件,而对于另外的动态内容请求可以转发到另外的动态服务进程中。

在实际的成产环境中,一个server 中可以定义非常多的location。而且在定义location时,不同的前缀可以对匹配的结果产生不同影响。如何正确地配置生产环境中总多location就变成一项很复杂的任务。

这篇文章,我们试着从配置,原理以及源代码的角度来分析这些配置的location是如何被组织起来的以及当一个服务请求怎么匹配到不同的location的。明白了这些原理对于我们正确配置生产环境中的location有很大的帮助。而且我们也可以从源代码角度理解Nginx是如何实现location匹配的。

二配置指令

与location配置相关的指令就是location。它的语法如下:

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

location @name { ... }

指令location有两种定义形式,一种是使用前缀字符,一种是使用正则表达式。如果是前缀字符,前面可以有=或^~修饰符。如果是正则表达式,前面有~或~*修饰符。具体的修饰符的意义如下:

= 表示精确匹配。只有请求的url路径与后面的字符串完全相等时,才会命中。

~ 表示该规则是使用正则定义的,区分大小写。

~* 表示该规则是使用正则定义的,不区分大小写。

^~ 表示如果该符号后面的字符是最佳匹配,采用该规则,不再进行后续的查找。

还有一种修饰符@用于定义一个内部 Location 块,该块不能被外部 Client 所访问,只能被 Nginx 内部配置指令所访问,比如 try_files 或者error_page。

Nginx在进行location匹配时,正则表达式的优先级比前缀字符优先级高。但是正则表达式的匹配是非常消耗cpu时间的,所以为了提高匹配效率,Nginx提供了=^~两个修饰符可以在某些情况下跳过location的正则表达式匹配。

按照上述的location的语法,如果在前缀字符定义location前面添加=符号,表明如果在URI与=后面的前缀表达式完全相等,则会跳过正则表达式匹配过程。如果不加这个=符号,即使在前缀匹配阶段URI和定义的location完全相等,也要再去依次匹配正则表达式定义的location。只要有一个正则表达式匹配成功,就会采用此正则表达式定义的location。

另外,在前缀字符串前加^~也可以跳过正则表达式匹配,加快location的执行速度。它不需要URI完全相等,只需要匹配上前缀而且此location是当下最长匹配即可跳过。

在配置location时,需要注意如下事项:

  • 指令location中的正则表达式,就像server_name中一样,可以用小括号()提取变量,供后续其他Nginx模块的指令使用。
  • Nginxlocation匹配是大小写敏感的。 正则表达式中提供了~*符号用来表示大小写无关。
  • 不包含正则的location 在配置文件中的顺序不会影响匹配顺序。而包含正则表达式的 location 会按照配置文件中定义的顺序进行匹配。
  • 设置为精确匹配 (with = prefix)  location 如果匹配请求 URI 的话,此location 被马上使用,匹配过程结束。
  • 在其它只包含普通字符的 location 中,找到和请求 URI 最长的匹配。如果此 server{}没有包含正则的 location或者该locaiton 启用了 ^~的话,这个最 长匹配的 location会被使用。如果此 server{}中包含正则的location,则先在 这些正则 location中进行匹配,如果找到匹配,则使用匹配的正则 location,如果 没找到匹配,依然使用最大匹配的 location
  • ^~, =, ~, ~*这些修饰符和后面的 URI 字符串中间可以不使用空格隔开。@ 修饰符必须和 URI 字符串 (实际上应该叫命名”) 直接连接。
  • location可以嵌套定义。但是要符合以下几条规则:
    • 修饰的 location中不能再嵌套其它location 
    • 修饰的 location中不能再嵌套其它 location
    • 修饰的 location不能嵌套到其它location 
    • location的 URI 字符串必须是子location  URI 字符串的前缀 (location 启用了正则的情况除外)

匹配流程

location匹配的核心原则,是最长匹配,具体流程是:

  1. 先对前缀location执行最长前缀匹配。
  2.  若最长前缀location前携带有=或者^~,那么使用此location配置块处理请求,不再进行正则匹配。对于=前缀,要求完全相等,对于^~前缀,只有最长匹配前有^~符号,才能跳过正则表达式。
  3. server{ }中正则表达式的出现顺序,依次匹配。成功后就选中此location正则表达式的查找是按照在配置文件中的顺序进行的。因此正则的顺序很重要,建议越精细的放的越靠前。
  4. 若所有正则表达式皆未匹配上,则使用第1步中检索出的最长前缀location处理请求。
  5. 如果在第1步中也没有匹配到最长location,则返回404。

让我们使用一个Nginx官网的例子来进一步说明location匹配过程:

假如我们有下面的一段配置文件:

location = / {

    [ configuration A ]

}

location / {

    [ configuration B ]

}

location /user/ {

    [ configuration C ]

}

location ^~ /images/ {

    [ configuration D ]

}

location ~* \.(gif|jpg|jpeg)$ {

    [ configuration E ]

}

请求/精准匹配A,不再往下查找。

请求/index.html匹配B。首先查找匹配的前缀字符,找到最长匹配是配置B,接着又按照顺序查找匹配的正则。结果没有找到,因此使用先前标记的最长匹配,即配置B。

请求/user/index.html匹配C。首先找到最长匹配C,由于后面没有匹配的正则,所以使用最长匹配C。

请求/user/1.jpg匹配E。首先进行前缀字符的查找,找到最长匹配项C,继续进行正则查找,找到匹配项E。因此使用E。

请求/images/1.jpg匹配D。首先进行前缀字符的查找,找到最长匹配D。但是,特殊的是它使用了^~修饰符,不再进行接下来的正则的匹配查找,因此使用D。这里,如果没有前面的修饰符,其实最终的匹配是E。大家可以想一想为什么。

请求/documents/about.html匹配B。因为B表示任何以/开头的URL都匹配。在上面的配置中,只有B能满足,所以匹配B。

四数据结构

与location匹配相关的数据结构有:

ngx_http_core_loc_conf_sngx_listening_t, ngx_http_port_t, ngx_http_in_addr_t, ngx_http_addr_conf_t, ngx_http_server_name_t, ngx_http_conf_port_t, ngx_http_conf_addr_t, ngx_http_request_t, ngx_http_connection_t,ngx_http_location_queue_t等。它们的关联关系如下图所示

 

ngx_http_core_loc_conf_s

ngx_http_core_loc_conf_s结构对应一个location块的配置,结构中的named, noname, exact_match和noregex flag用来区分location定义中的几个修饰符。

ngx_http_location_queue_t

结构体ngx_http_location_queue_t用于临时保存location的队列。其中如果location是exact_match, regex, named或者noname类型的,则会把location结构存放到exact成员变量中。如果是非exact类型,则存放到inclusive变量中。

五源码分析

配置平面

与配置平面有关的逻辑就是解析location指令并且构建用于查找的URI数据结构。

指令location的解释函数是ngx_http_core_location。

函数流程为:

  1. 函数会为location生成一个ngx_http_core_loc_conf_s结构用来存储location的定义。
  2. 首先会根据location的描述符正确生成flags。如果带有@修饰符,则把named flag设置为1。如果是通过if语句定义的,把noname flag设置为1.如果有=修饰符,则exact_match flag会设置为1. 如果有^=符号,则noregex flag会设置为1.
  3. 对配置进行一些有效性检查。比如带@修饰符的location不能再嵌入到任何别的location里面,而且它本身也不能再嵌入任何别的location等。
  4. 把最初生成的ngx_http_core_loc_conf_s结构结果通过函数ngx_http_add_location加入到上级ngx_http_core_location中的locations这个queue里面。
  5. 函数ngx_http_add_location会先成生一个ngx_http_location_queue_t结构,然后根据ngx_http_core_loc_conf_s中的exact_match,regex, named, nonamed flag是否有一个为1,如果条件满足则选择把ngx_http_core_loc_conf_s放入到ngx_http_location_queue_t结构中的exact变量中,反之则会把它放入inclusive变量中。这里说明一下inclusive,它表示URI之间的包含关系,即”/abc/a“这个URI是包含”/abc“的。

指令解析完毕后会调用ngx_http_init_locationslocation queue中的所有location进行排序以及分类存储。

函数流程为:

  1. 调用ngx_queue_sort对location queue中的所有locations按照exact(sorted) -> inclusive(sorted) -> regex -> named -> noname的原则进行排序。
  2. 因为location中还可以再嵌入location。所以,函数ngx_http_init_locations会递归调用,去处理嵌套情况。
  3. 把所有named的location从location queue里面分离出来然后加入到对应server结构ngx_http_core_srv_conf_t 中的named_locations里面。
  4. 将正则表达式定义的location分离出来,放置到ngx_http_core_loc_conf_t结构中的regex_locations成员中。
  5. nonamed的location从location queue中删除。

可以看到,以上流程完成之后,原先的location队列就只剩下经过排序后的exact以及inclusive类型的location了。这两类location对应配置文件中的定义,就是不含修饰符的location,带有=和^~前缀的location。

接着调用ngx_http_init_static_location_trees函数做进一步的处理。

函数流程为:

  1. 完整性检测,比如判断locations queue是否为空,如果是空则直接返回。
  2. 因为location可以嵌套,所以,函数ngx_http_init_locations递归调用,去处理嵌套情况。
  3. 调用ngx_http_join_exact_locations把location queue中将当前虚拟主机中 URI字符串完全一致的 exact inclusive 类型的 location 进行合并。
  4. 调用ngx_http_create_locations_listlocations queue变成locations list。如下图所示就是ngx_http_create_locations_list调用前后的效果。

  1. 调用函数ngx_http_create_locations_tree,在上述创建的list的基础上,进一步生成查找树。首先确定locaiton队列的中间节点,然后从中间节点把location分成两部分。分别再构造左右子树。最后把inclusive类型的location放入到成员tree中。

如下图所示就是ngx-location-create-locations-tree调用前后的效果:

至此,所有和location相关的数据结构都已经初始化和创建完毕。

数据平面

上述数据结构创建起来以后,等HTTP请求到达时,在处理HTTP请求的FIND_CONFIG阶段的ngx_http_core_find_config_phase 函数会调用ngx_http_core_find_location完成location的匹配查找。

查找location流:

函数ngx_http_core_find_location 会调用ngx_http_core_find_static_location 在static_location这一多叉树成员中按照最长匹配查找非regex配置的location。这个过程本质上就是根据前面构建好的树结构,进行二分查找。查找完static_location以后,如果需要再遍历所有的regex进行优先匹配。最后根据配置和查找到的结果返回查找到的location.

六结语

匹配server和location是Nginx进行请求转发的核心过程。把某一个请求正确匹配到server和location以后,就可以通过server和location的配置开始对此请求开始服务了。对于location的匹配,为了提高匹配效率,Nginx把正则表达式和非正则表达式配置的location进行了区分,并且把非正则表达式组织成了多叉树,把正则表达式组织成了list。对于前者,匹配的原则是最长匹配,而对于后者是采用优先匹配,也就是第一个匹配到的就是匹配结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值