
1 定义ngx_http_init_locations 函数 定义在 ./nginx-1.24.0/src/http/ngx_http.cstaticngx_int_tngx_http_init_locations(ngx_conf_t*cf,ngx_http_core_srv_conf_t*cscf,ngx_http_core_loc_conf_t*pclcf){ngx_uint_tn;ngx_queue_t*q,*locations,*named,tail;ngx_http_core_loc_conf_t*clcf;ngx_http_location_queue_t*lq;ngx_http_core_loc_conf_t**clcfp;#if(NGX_PCRE)ngx_uint_tr;ngx_queue_t*regex;#endiflocationspclcf-locations;if(locationsNULL){returnNGX_OK;}ngx_queue_sort(locations,ngx_http_cmp_locations);namedNULL;n0;#if(NGX_PCRE)regexNULL;r0;#endiffor(qngx_queue_head(locations);q!ngx_queue_sentinel(locations);qngx_queue_next(q)){lq(ngx_http_location_queue_t*)q;clcflq-exact?lq-exact:lq-inclusive;if(ngx_http_init_locations(cf,NULL,clcf)!NGX_OK){returnNGX_ERROR;}#if(NGX_PCRE)if(clcf-regex){r;if(regexNULL){regexq;}continue;}#endifif(clcf-named){n;if(namedNULL){namedq;}continue;}if(clcf-noname){break;}}if(q!ngx_queue_sentinel(locations)){ngx_queue_split(locations,q,tail);}if(named){clcfpngx_palloc(cf-pool,(n1)*sizeof(ngx_http_core_loc_conf_t*));if(clcfpNULL){returnNGX_ERROR;}cscf-named_locationsclcfp;for(qnamed;q!ngx_queue_sentinel(locations);qngx_queue_next(q)){lq(ngx_http_location_queue_t*)q;*(clcfp)lq-exact;}*clcfpNULL;ngx_queue_split(locations,named,tail);}#if(NGX_PCRE)if(regex){clcfpngx_palloc(cf-pool,(r1)*sizeof(ngx_http_core_loc_conf_t*));if(clcfpNULL){returnNGX_ERROR;}pclcf-regex_locationsclcfp;for(qregex;q!ngx_queue_sentinel(locations);qngx_queue_next(q)){lq(ngx_http_location_queue_t*)q;*(clcfp)lq-exact;}*clcfpNULL;ngx_queue_split(locations,regex,tail);}#endifreturnNGX_OK;}ngx_http_init_locations 函数的作用是 对 location 配置进行整理、排序与分类组织2 详解1 函数签名staticngx_int_tngx_http_init_locations(ngx_conf_t*cf,ngx_http_core_srv_conf_t*cscf,ngx_http_core_loc_conf_t*pclcf)返回值 NGX_OK为 0函数执行成功。 NGX_ERROR为 -1函数执行失败参数 ngx_conf_t *cf 指向配置上下文 ngx_http_core_srv_conf_t *cscf 指向当前服务器server配置的指针 ngx_http_core_loc_conf_t *pclcf 指向父级 location 配置的指针2 逻辑流程1 局部变量 2 locations 排序 3 初始化用于分类统计的变量 4 遍历 location 链表 4-1 递归调用 4-2 统计标记正则表达式类型的 location 4-3 统计标记命名 location 4-4 noname location 5 分割默认 location 6 分割命名 location 7 分割正则 location 8 返回成功1 局部变量{ngx_uint_tn;ngx_queue_t*q,*locations,*named,tail;ngx_http_core_loc_conf_t*clcf;ngx_http_location_queue_t*lq;ngx_http_core_loc_conf_t**clcfp;#if(NGX_PCRE)ngx_uint_tr;ngx_queue_t*regex;#endif2 locations 排序locationspclcf-locations;if(locationsNULL){returnNGX_OK;}ngx_queue_sort(locations,ngx_http_cmp_locations);#1 获取需要处理的子 location 队列#2 判断 locations 是否为空指针。 如果 pclcf-locations 为 NULL说明当前父级 location 下没有定义任何子 location 不需要再做任何处理 函数直接返回成功 NGX_OK#3 对子 location 队列进行排序3 初始化用于分类统计的变量namedNULL;n0;#if(NGX_PCRE)regexNULL;r0;#endifnamed 用于记录队列中遇到的第一个命名 location 将整型计数器 n 初始化为 0。 n 用于统计命名 location 的数量 regex 用于记录队列中遇到的第一个正则 location r 用于统计正则 location 的数量4 遍历 location 链表for(qngx_queue_head(locations);q!ngx_queue_sentinel(locations);qngx_queue_next(q)){lq(ngx_http_location_queue_t*)q;clcflq-exact?lq-exact:lq-inclusive;4-1 递归调用if(ngx_http_init_locations(cf,NULL,clcf)!NGX_OK){returnNGX_ERROR;}递归调用自身对当前子 location 的嵌套子 location 进行同样的初始化4-2 统计标记正则表达式类型的 location#if(NGX_PCRE)if(clcf-regex){r;if(regexNULL){regexq;}continue;}#endifregex 指向队列中第一个正则 location 的节点 记录下正则 location 区块在链表中的起始节点4-3 统计标记命名 locationif(clcf-named){n;if(namedNULL){namedq;}continue;}4-4 noname locationif(clcf-noname){break;}}检测当前 location 是否为“noname”类型即默认的 location /兜底匹配 并在遇到时立即终止循环 由于 noname location 在排序后的队列中一定位于最后因为它的匹配优先级最低 一旦遇到它就意味着后续的所有节点都属于它或者已经没有其他节点了。 因此不需要继续遍历 将 q 指针停留在当前 noname 节点上 便于循环结束后用 ngx_queue_split(locations, q, tail) 将包括 noname 在内的剩余部分从原队列中分离出去 使普通 location 队列不再包含兜底 location5 分割默认 locationif(q!ngx_queue_sentinel(locations)){ngx_queue_split(locations,q,tail);}#1 判断当前指针 q 是否不等于队列 locations 的哨兵节点。 在之前的 for 循环中q 可能是两种情况 如果循环正常结束即没有遇到任何 noname location 那么 q 会等于 哨兵 如果循环因为遇到 clcf-noname 而执行了 break 那么 q 会停留在那个 noname 节点上不是哨兵。 只有确实存在兜底 location 时才需要进行队列分割避免无谓的操作。#2 将队列 locations 从节点 q 处分割成两个独立队列。 locations 原队列的指针分割后它将被修改为只包含从头部到 q 之前的所有节点不包含 q。 q 分割点节点。 tail 一个 ngx_queue_t 指针用于接收分割后的尾部队列包含 q 及其之后的所有节点。 将兜底 locationnoname以及可能跟在它后面的其他节点 实际上在排序后noname 应该是队列中最后一个但代码为了健壮性仍处理了后续节点 从主队列中剥离出来。 这样后续对普通 location字符串匹配的处理就不会再受到兜底 location 的干扰。 兜底 location 会被单独保存在请求匹配的最后阶段使用。6 分割命名 locationif(named){clcfpngx_palloc(cf-pool,(n1)*sizeof(ngx_http_core_loc_conf_t*));if(clcfpNULL){returnNGX_ERROR;}cscf-named_locationsclcfp;for(qnamed;q!ngx_queue_sentinel(locations);qngx_queue_next(q)){lq(ngx_http_location_queue_t*)q;*(clcfp)lq-exact;}*clcfpNULL;ngx_queue_split(locations,named,tail);}#1 检查 named 指针是否为非空。 named 在之前的循环中若遇到了命名 location 就会被赋值为第一个命名 location 的节点地址 如果整个队列中没有命名 locationnamed 保持 NULL。 如果为 NULL说明当前 server 块下没有任何命名 location跳过整个处理块 避免无效处理。#2 clcfp ngx_palloc(cf-pool, (n 1) * sizeof(ngx_http_core_loc_conf_t *)); 从配置池 cf-pool 中分配一块内存用于存储命名 location 的配置指针数组。 (n 1) * sizeof(ngx_http_core_loc_conf_t *) 数组长度为 n1其中 n 是命名 location 的数量。 多出的一个位置用于存放 NULL 作为哨兵方便运行时遍历。 意义为服务器配置准备一个连续的内存区域用于存放所有命名 location 的配置指针。#3 cscf-named_locations clcfp; 将刚刚分配的数组指针赋值给服务器配置的 named_locations 字段。#4 for (q named; q ! ngx_queue_sentinel(locations); q ngx_queue_next(q)) 遍历从 named 节点开始直到队列尾部的所有节点。 由于之前的循环已经将命名 location 放在队列的连续段中并且 named 指向该段的第一个节点 命名 location 位于链表的尾部排序优先级最低。 因此从 named 到链表末尾的所有节点都是命名 location。 这个循环将遍历所有命名 location。 逐个处理命名 location 节点将其配置指针存入数组。#5 *clcfp NULL; 将数组的最后一个元素设置为 NULL。 循环结束后clcfp 指向数组最后一个元素的下一个位置将其赋值为 NULL 作为哨兵。#6 ngx_queue_split(locations, named, tail); 将队列 locations 从 named 节点处分割成两个队列。 locations 原队列指针分割后它将被修改为只包含从头部到 named 之前的所有节点。 named 分割点节点第一个命名 location。 tail 一个 ngx_queue_t 指针用于接收分割后的尾部队列包含 named 及其之后的所有节点。 分割后所有命名 location 从 locations 队列中移除形成一个独立的队列 存储在 tail 变量中但后续未再使用因为命名 location 已经被单独存储到了数组中不需要再保留队列。 意义 确保 locations 队列中不再包含命名 location 只保留普通字符串匹配的 location以便后续处理。7 分割正则 location#if(NGX_PCRE)if(regex){clcfpngx_palloc(cf-pool,(r1)*sizeof(ngx_http_core_loc_conf_t*));if(clcfpNULL){returnNGX_ERROR;}pclcf-regex_locationsclcfp;for(qregex;q!ngx_queue_sentinel(locations);qngx_queue_next(q)){lq(ngx_http_location_queue_t*)q;*(clcfp)lq-exact;}*clcfpNULL;ngx_queue_split(locations,regex,tail);}#endif#1 通过 ngx_queue_split 将正则节点从主链表中移除 确保剩下的 locations 链表只包含普通前缀 location 用于构建高效的前缀查找树。#2 至此ngx_http_init_locations 函数成功将混合的 location 链表拆分成了三个独立的部分 普通 Location 链表保留在 locations后续建树。 正则 Location 数组pclcf-regex_locations。 命名 Location 数组cscf-named_locations。8 返回成功returnNGX_OK;}