1 定义ngx_http_create_locations_list 函数 定义在 ./nginx-1.24.0/src/http/ngx_http.cstaticvoidngx_http_create_locations_list(ngx_queue_t*locations,ngx_queue_t*q){u_char*name;size_tlen;ngx_queue_t*x,tail;ngx_http_location_queue_t*lq,*lx;if(qngx_queue_last(locations)){return;}lq(ngx_http_location_queue_t*)q;if(lq-inclusiveNULL){ngx_http_create_locations_list(locations,ngx_queue_next(q));return;}lenlq-name-len;namelq-name-data;for(xngx_queue_next(q);x!ngx_queue_sentinel(locations);xngx_queue_next(x)){lx(ngx_http_location_queue_t*)x;if(lenlx-name-len||ngx_filename_cmp(name,lx-name-data,len)!0){break;}}qngx_queue_next(q);if(qx){ngx_http_create_locations_list(locations,x);return;}ngx_queue_split(locations,q,tail);ngx_queue_add(lq-list,tail);if(xngx_queue_sentinel(locations)){ngx_http_create_locations_list(lq-list,ngx_queue_head(lq-list));return;}ngx_queue_split(lq-list,x,tail);ngx_queue_add(locations,tail);ngx_http_create_locations_list(lq-list,ngx_queue_head(lq-list));ngx_http_create_locations_list(locations,x);}ngx_http_create_locations_list 函数的作用是 将 Nginx 配置中线性排列的 location 列表 按照路径前缀的包含关系递归拆分为多级嵌套的树形结构 建立层次化的 location 组织关系。2 详解1 函数签名staticvoidngx_http_create_locations_list(ngx_queue_t*locations,ngx_queue_t*q)参数 ngx_queue_t *locations 当前正在处理的 location 链表的头节点或哨兵节点。 ngx_queue_t *q 它是 locations 链表中的一个节点。 函数会检查 q 所代表的 location 配置是否具有“包含性”inclusive。 如果 q 可以作为父节点函数会遍历 q 之后的节点寻找属于 q 的子节点并将它们挂载到 q 下。 它是递归处理的起点处理完 q 的子树后函数会继续处理链表中的后续节点。返回值 void函数不返回任何值2 逻辑流程1 局部变量1 局部变量 2 边界检查 3 判断当前 location 是否具有“包容”子 location 的能力 4 遍历查找相同前缀的节点 5 检查当前 location 是否真的拥有子节点 6 分割相同前缀的节点并挂载到当前节点下形成层级结构 7 处理后续节点{u_char*name;size_tlen;ngx_queue_t*x,tail;ngx_http_location_queue_t*lq,*lx;2 边界检查if(qngx_queue_last(locations)){return;}已经是最后一个节点 后面没有节点需要处理了3 判断当前 location 是否具有“包容”子 location 的能力lq(ngx_http_location_queue_t*)q;if(lq-inclusiveNULL){ngx_http_create_locations_list(locations,ngx_queue_next(q));return;}如果 inclusive NULL 则当前 location 不会对后续元素产生“归属”关系无需构建子列表。 递归调用处理下一个节点4 遍历查找相同前缀的节点lenlq-name-len;namelq-name-data;for(xngx_queue_next(q);x!ngx_queue_sentinel(locations);xngx_queue_next(x)){lx(ngx_http_location_queue_t*)x;if(lenlx-name-len||ngx_filename_cmp(name,lx-name-data,len)!0){break;}}#1 获取当前 location 的名称长度和指针#2 从当前 location 之后开始 逐个检查后续的 location 直到遇到第一个 不满足前缀匹配条件 的元素或到达队列末尾。#3 判断当前检查的 location 是否前缀匹配 匹配条件有两个必须 同时满足 才认为是匹配的继续循环 如果 任一条件不满足则执行 break 退出循环。 条件一len lx-name-len 如果当前 location 的名称长度 大于 后续 location 的名称长度 则不可能前缀匹配因为后续 location 比当前还短无法包含当前的前缀。 条件二ngx_filename_cmp(name, lx-name-data, len) ! 0 调用 ngx_filename_cmp 比较两个字符串的前 len 个字符是否相等。 ngx_filename_cmp 是 Nginx 内部用于路径比较的函数 如果前 len 个字符不相同则不是前缀匹配。 若上述任一条件为真则 break 退出循环。 若两个条件都不成立即 len lx-name-len 且前 len 个字符相等 则当前检查的 location 是当前 location 的 前缀匹配子节点循环继续检查下一个后续元素。 循环结束后的状态 当遇到第一个 不满足前缀匹配 的元素时break 跳出循环此时 x 指向该不匹配的元素或哨兵。 所有从 ngx_queue_next(q) 开始 到 x 之前 的元素即已经遍历过的那些都是前缀匹配的 应成为当前 location 的子节点。 而 x 及其之后的元素则不属于当前 location 的子节点 后续代码会将它们从当前队列中拆分出来5 检查当前 location 是否真的拥有子节点qngx_queue_next(q);if(qx){ngx_http_create_locations_list(locations,x);return;}x 是前面 for 循环中找到的第一个不满足前缀匹配的节点或队列哨兵。 如果 q x说明从当前 location 的下一个节点开始第一个节点就已经不匹配了。 说明在原父节点和边界 x 之间没有任何节点 换句话说没有任何节点可以作为当前 location 的子节点。 为什么可能相等 因为 for 循环的条件是从 ngx_queue_next(q) 开始遍历 如果第一个元素就不满足匹配条件循环会立即 break 此时 x 就等于 ngx_queue_next(q)即新的 q。6 分割相同前缀的节点 并挂载到当前节点下形成层级结构ngx_queue_split(locations,q,tail);ngx_queue_add(lq-list,tail);if(xngx_queue_sentinel(locations)){ngx_http_create_locations_list(lq-list,ngx_queue_head(lq-list));return;}ngx_queue_split(lq-list,x,tail);ngx_queue_add(locations,tail);ngx_http_create_locations_list(lq-list,ngx_queue_head(lq-list));#1 将主链表 locations 在节点 q 处切割成两个独立的链表#2 将切割出来的 tail 链表添加到当前父节点 lq 的子列表 lq-list 中 层级挂载正式建立父子关系。 原本平级的节点现在在逻辑上成为了当前 location 的子节点集合#3 判断之前找到的边界节点 x 是否是主链表的哨兵节点头节点。 如果 x 等于哨兵节点说明之前的循环一直遍历到了链表尽头都没有遇到不匹配的节点 这意味着当前父节点之后的所有剩余节点都是它的子节点不存在“兄弟节点”或“无关节点”。 优化路径既然没有无关节点需要归还给主链表就可以跳过后续的“再次切割”逻辑直接处理子列表 递归调用当前函数整理刚刚挂载好的子列表 当前层级的任务已全部完成 return#4 将当前父节点的子列表 lq-list 在节点 x 处再次切割 将不属于当前子树的节点分离到 tail 中 将切割出来的 tail 链表非子节点部分重新添加回主链表 locations 的末尾#5 递归调用当前函数处理子列表7 处理后续节点ngx_http_create_locations_list(locations,x);}递归调用 它负责继续处理主链表中剩余的节点完成整个 location 树的构建