1 定义ngx_http_variables_init_vars 函数 定义在 ./nginx-1.24.0/src/http/ngx_http_variables.cngx_int_tngx_http_variables_init_vars(ngx_conf_t*cf){size_tlen;ngx_uint_ti,n;ngx_hash_key_t*key;ngx_hash_init_thash;ngx_http_variable_t*v,*av,*pv;ngx_http_core_main_conf_t*cmcf;/* set the handlers for the indexed http variables */cmcfngx_http_conf_get_module_main_conf(cf,ngx_http_core_module);vcmcf-variables.elts;pvcmcf-prefix_variables.elts;keycmcf-variables_keys-keys.elts;for(i0;icmcf-variables.nelts;i){for(n0;ncmcf-variables_keys-keys.nelts;n){avkey[n].value;if(v[i].name.lenkey[n].key.lenngx_strncmp(v[i].name.data,key[n].key.data,v[i].name.len)0){v[i].get_handlerav-get_handler;v[i].dataav-data;av-flags|NGX_HTTP_VAR_INDEXED;v[i].flagsav-flags;av-indexi;if(av-get_handlerNULL||(av-flagsNGX_HTTP_VAR_WEAK)){break;}gotonext;}}len0;avNULL;for(n0;ncmcf-prefix_variables.nelts;n){if(v[i].name.lenpv[n].name.lenv[i].name.lenlenngx_strncmp(v[i].name.data,pv[n].name.data,pv[n].name.len)0){avpv[n];lenpv[n].name.len;}}if(av){v[i].get_handlerav-get_handler;v[i].data(uintptr_t)v[i].name;v[i].flagsav-flags;gotonext;}if(v[i].get_handlerNULL){ngx_log_error(NGX_LOG_EMERG,cf-log,0,unknown \%V\ variable,v[i].name);returnNGX_ERROR;}next:continue;}for(n0;ncmcf-variables_keys-keys.nelts;n){avkey[n].value;if(av-flagsNGX_HTTP_VAR_NOHASH){key[n].key.dataNULL;}}hash.hashcmcf-variables_hash;hash.keyngx_hash_key;hash.max_sizecmcf-variables_hash_max_size;hash.bucket_sizecmcf-variables_hash_bucket_size;hash.namevariables_hash;hash.poolcf-pool;hash.temp_poolNULL;if(ngx_hash_init(hash,cmcf-variables_keys-keys.elts,cmcf-variables_keys-keys.nelts)!NGX_OK){returnNGX_ERROR;}cmcf-variables_keysNULL;returnNGX_OK;}ngx_http_variables_init_vars 函数的作用是 完成 Nginx HTTP 变量系统的初始化整合 它将配置解析阶段临时保存的变量定义信息variables_keys与 配置中实际引用的变量列表variables进行匹配融合 并为运行时构建快速查找的哈希表variables_hash。 同时该函数处理动态前缀变量如 arg_、http_的绑定逻辑并清理临时数据结构。2 详解1 函数签名ngx_int_tngx_http_variables_init_vars(ngx_conf_t*cf)返回值 返回 NGX_OK宏定义为 0表示函数执行成功 返回 NGX_ERROR宏定义为 -1表示函数执行失败参数 ngx_conf_t *cf 指向配置解析上下文2 逻辑流程1 局部变量 2 查找变量定义 2-1 精确匹配 2-2 前缀匹配 2-3 未定义 3 构建变量 hash 表 3-1 标记不需要 hash 表的数据 3-2 hash 表参数设置 3-3 创建 hsah 表 4 返回成功1 局部变量{size_tlen;ngx_uint_ti,n;ngx_hash_key_t*key;ngx_hash_init_thash;ngx_http_variable_t*v,*av,*pv;ngx_http_core_main_conf_t*cmcf;2 查找变量定义/* set the handlers for the indexed http variables */cmcfngx_http_conf_get_module_main_conf(cf,ngx_http_core_module);vcmcf-variables.elts;pvcmcf-prefix_variables.elts;keycmcf-variables_keys-keys.elts;#1 获取 HTTP 核心模块ngx_http_core_module在当前配置上下文中的主配置结构体指针。 #2 获取 cmcf-variables 动态数组的元素内存区域起始地址并赋值给指针 v 记录实际出现的变量实例。 这是待处理的目标清单。 后续循环将遍历 v为每个变量实例注入运行逻辑并分配索引 #3 获取 cmcf-prefix_variables 动态数组的元素内存区域起始地址并赋值给指针 pv 它存储的是前缀变量定义 #4 获取 cmcf-variables_keys-keys 动态数组的元素内存区域起始地址并赋值给指针 key。 cmcf-variables_keys 是一个指向 ngx_hash_keys_arrays_t 结构体的指针。 该结构体是 Nginx 用于构建哈希表的临时容器。 key 指向的数组是主要数据源。 它包含了所有通过 ngx_http_add_variable 函数添加的变量完整定义包括内置变量和通过 set 指令定义的变量。2-1 精确匹配for(i0;icmcf-variables.nelts;i){for(n0;ncmcf-variables_keys-keys.nelts;n){avkey[n].value;if(v[i].name.lenkey[n].key.lenngx_strncmp(v[i].name.data,key[n].key.data,v[i].name.len)0){v[i].get_handlerav-get_handler;v[i].dataav-data;av-flags|NGX_HTTP_VAR_INDEXED;v[i].flagsav-flags;av-indexi;if(av-get_handlerNULL||(av-flagsNGX_HTTP_VAR_WEAK)){break;}gotonext;}}#1 遍历所有被引用的变量 遍历 cmcf-variables 数组中的每一个元素。 这个数组存放了在配置文件上下文里实际出现过的变量 cmcf-variables.nelts 是该数组的元素个数。 i 既是循环计数器也是当前处理变量在 variables 数组中的索引 这个索引在后续会被反向记录到源定义中。#2 在变量定义哈希表临时容器中查找精确匹配 遍历 variables_keys-keys 数组该数组包含了所有已注册的变量完整定义 包括 Nginx 内置变量和通过 set 指令定义的变量。 cmcf-variables_keys-keys.nelts 是定义总数。 每次外层处理一个 v[i]内层都要从头遍历定义池 直到找到名称完全一致的定义。 cmcf-variables 中保存的是配置文件中用到的变量 是配置文件中的引用清单 cmcf-variables_keys 中保存的是所有变量的定义 是系统的全局定义字典 外层循环取一个变量 然后用内层循环来查找这个变量的定义 就是用配置实例去查定义字典完成运行时装配#3 av 指向当前遍历到的变量的定义 av 代表 Active Variable源变量即变量的权威定义#4 精确名称匹配判定 先比长度再比内容。长度不等直接短路避免无效内存比较。#4-1 将定义中的取值函数指针和自定义数据直接拷贝到运行时实例 get_handler 是一个函数指针当 Nginx 运行时需要获取该变量的值时就会调用此函数。 data 是一个通用的 uintptr_t 字段用于传递给 get_handler 的上下文信息 完成“变量名”到“执行逻辑”的映射 这是变量初始化最关键的一步。在此之前 v[i] 只是一个仅有名称的空壳 现在它获得了真正的“灵魂”——即知道如何计算自己值的逻辑#4-2 标志位同步与索引标记 在源变量 av 的标志位中置上 NGX_HTTP_VAR_INDEXED 标志 该宏标志表示“此变量已经被分配了一个运行时索引”。 一旦设置后续处理会意识到该变量可以通过索引快速访问。 将更新后的标志位包括 INDEXED 标志同步到目标变量 v[i] 将目标变量在 variables 数组中的索引号 i 反向记录到源变量 av 的 index 字段 意义 建立反向索引。运行时通过变量名查找到 av 后 可以直接读取 av-index 得知该变量在请求的 r-variables 动态数组中的位置 从而在 O(1) 时间内完成值的存取。这是 Nginx 变量系统高性能的核心设计之一。#5 弱变量与无处理函数的特殊情况 条件一av-get_handler NULL 含义该变量没有取值函数例如某些仅作为内部占位符的变量。 条件二av-flags NGX_HTTP_VAR_WEAK 含义该变量被标记为“弱变量”weak variable 弱变量的特点是允许被后续的同名变量覆盖例如通过 set 指令重新定义。 执行动作break; 跳出内层循环。 否则执行 goto next直接跳到外层循环末尾跳过后续逻辑#6 goto next 是性能优化 因为精确匹配已经成功变量已经获得完整的处理逻辑不再需要进行后备的前缀匹配#7 逻辑流程 外层循环: 遍历 v[i] (实际引用的变量) │ ├─ 内层循环: 遍历 key[n] (所有已定义变量) │ │ │ ├─ 名称长度相同 内容相同 │ │ │ │ │ ├─ 是 → 拷贝 handler / data / flags │ │ │ 设置 INDEXED 标志和反向索引 │ │ │ │ │ │ │ ├─ 是弱变量或无handler → break 内层循环 │ │ │ │ │ │ │ └─ goto next (跳过后续前缀匹配) │ │ │ │ │ └─ 否 → 继续内层循环 │ │ │ └─ 内层循环结束未精确匹配 → 进入前缀匹配逻辑 │ └─ next: 继续外层循环2-2 前缀匹配len0;avNULL;for(n0;ncmcf-prefix_variables.nelts;n){if(v[i].name.lenpv[n].name.lenv[i].name.lenlenngx_strncmp(v[i].name.data,pv[n].name.data,pv[n].name.len)0){avpv[n];lenpv[n].name.len;}}前缀匹配 当一个变量在 variables_keys 中找不到精确的同名定义时 系统会在此处尝试为其寻找一个匹配其名称前缀的变量定义 例如 $http_host 匹配前缀 http_$arg_id 匹配前缀 arg_。#1 初始化前缀匹配的临时记录变量。 len用于记录当前已找到的最长匹配前缀的长度。初始值为 0表示尚未找到任何匹配。 av指针指向当前已找到的最优匹配前缀变量结构体。初始为 NULL。 为什么要找“最长”前缀 因为可能存在前缀重叠的情况 例如 http_ 和 http_foo_ 都是合法前缀虽然实际 Nginx 中不会这样设计。 通过记录最长的匹配前缀可以确保为变量选择最具体、最精确的处理逻辑。#2 遍历 cmcf-prefix_variables 数组该数组存放了所有前缀型变量定义 前缀变量是由 Nginx 核心在启动时预先注册的例如 http_处理请求头变量$http_user_agent arg_处理 URL 参数变量$arg_name cookie_处理 Cookie 变量$cookie_uid upstream_http_处理后端响应头变量 这些变量定义的特点是它们的 get_handler 能够解析变量名的剩余部分从而动态获取对应的值 前缀变量数量极少通常 10~15 个线性遍历开销很小#3 匹配判断 判断当前被引用的变量 v[i] 是否以 pv[n] 的名称开头且该前缀是当前找到的最长匹配。 v[i].name.len pv[n].name.len 被引用变量的名称长度必须大于或等于前缀变量的名称长度。 一个字符串要包含某个前缀其总长度必须不小于前缀长度。 v[i].name.len len 当前前缀长度 已记录的最长长度 ngx_strncmp(v[i].name.data, pv[n].name.data, pv[n].name.len) 0 比较被引用变量的前 pv[n].name.len 个字符是否与前缀变量的名称完全一致。 确认该变量名确实以 pv[n] 的名称开头。#4 更新当前找到的最优匹配 av 被设置为指向当前遍历到的前缀变量 pv[n] 的地址。 len 被更新为该前缀的长度作为后续循环的比较基准。 意义 循环结束后av 将指向最长匹配前缀的变量定义len 则是其前缀长度。 如果没有任何匹配av 仍为 NULL。if(av){v[i].get_handlerav-get_handler;v[i].data(uintptr_t)v[i].name;v[i].flagsav-flags;gotonext;}#1 检查前缀匹配是否成功。如果 av 不为 NULL 表示在前缀遍历中找到了匹配项。#2 将前缀变量 av 的取值回调函数指针赋值给目标变量 v[i] 将目标变量自身的名称字段地址强制转换为整数类型存入 data 字段 用处理器在运行时需要知道“完整变量名”才能动态截取后缀 如从 $http_user_agent 中提取 user_agent。 将变量名地址直接塞进 data运行时无需额外分配内存或全局查找。 将前缀变量 av 的标志位复制给目标变量 v[i]#3 找到了定义后续代码不需要执行 使用 goto 可以清晰地跳过本不需要执行的代码块2-3 未定义if(v[i].get_handlerNULL){ngx_log_error(NGX_LOG_EMERG,cf-log,0,unknown \%V\ variable,v[i].name);returnNGX_ERROR;}检查当前正在处理的变量 v[i] 是否仍然没有设置取值回调函数 也就是意味着该变量未定义 get_handler 是变量在运行时能够计算出值的唯一途径。 一个没有处理函数的变量就如同一个没有定义函数的函数名在运行时将完全无法工作 向 Nginx 的错误日志记录一条紧急级别的错误信息next:continue;}继续处理下一个变量3 构建变量 hash 表3-1 标记不需要 hash 表的数据for(n0;ncmcf-variables_keys-keys.nelts;n){avkey[n].value;if(av-flagsNGX_HTTP_VAR_NOHASH){key[n].key.dataNULL;}}#1 遍历 variables_keys-keys 数组中的每一个元素#2 取出当前数组元素对应的变量定义结构体指针#3 检查该变量的标志位中是否设置了 NGX_HTTP_VAR_NOHASH NGX_HTTP_VAR_NOHASH 是一个宏定义表示该变量不应被放入哈希表 为什么会有这样的变量呢 通常是因为 某些变量仅在配置解析阶段使用运行时不需要通过名称查找例如用于内部处理的临时变量。 某些变量有特殊的访问方式比如通过索引直接访问不需要占用哈希表空间。 可能是用户通过 set 指令定义的变量但后来被标记为不哈希虽然常见情况是用户变量默认会放入哈希表。 将该键值对中的 key.data 指针置为 NULL。 在后续调用 ngx_hash_init 构建哈希表时该函数会遍历传入的键值对数组仅处理 key.data 非空的元素。 若 key.data 为 NULL则直接跳过该条目。 这是一种轻量级的过滤机制。通过简单地将指针置空 就可在不重新构建数组或移动内存的前提下 让特定条目在哈希表构建阶段被忽略。在 Nginx 变量系统中变量的访问方式有两种 通过哈希表查找 运行时通过变量名字符串在 variables_hash 中定位到变量定义然后获得其索引和处理器。 通过索引直接访问 对于那些在配置解析阶段已经被分配了索引index且标记为 NGX_HTTP_VAR_INDEXED 的变量 运行时可以直接通过 r-variables[index] 快速操作无需哈希查找。 那些标记了 NGX_HTTP_VAR_NOHASH 的变量通常属于以下情况之一 它们只在配置解析时存在运行时根本不会通过名称被查找。 它们已经通过索引被访问哈希表对它们来说是冗余的。 它们是某些前缀变量访问逻辑已经由前缀处理器接管无需单独在哈希表中占位。 如果将这些变量也加入哈希表不仅浪费内存 还可能在极少数情况下造成哈希冲突或查找歧义。 因此在构建哈希表前将它们从键数组中“摘除”是必要的。3-2 hash 表参数设置hash.hashcmcf-variables_hash;hash.keyngx_hash_key;hash.max_sizecmcf-variables_hash_max_size;hash.bucket_sizecmcf-variables_hash_bucket_size;hash.namevariables_hash;hash.poolcf-pool;hash.temp_poolNULL;填充 ngx_hash_init_t 结构体 为接下来调用 ngx_hash_init() 构建最终的运行时变量哈希表准备参数。 #1 指定哈希表构建完成后存储的目标地址。 #2 指定用于计算哈希值的散列函数。 #3 设置哈希表的最大桶数量即哈希表的总槽位数上限 #4 设置每个哈希桶的最大字节容量。 #5 为本次哈希表初始化操作指定一个名称标识 #6 指定哈希表构建过程中所用的内存池。 #7 指定临时内存池此处设为 NULL3-3 创建 hsah 表if(ngx_hash_init(hash,cmcf-variables_keys-keys.elts,cmcf-variables_keys-keys.nelts)!NGX_OK){returnNGX_ERROR;}cmcf-variables_keysNULL;#1 实际构建运行时哈希表 #2 将指向临时容器 variables_keys 的指针置为空指针。 原始的 variables_keys 已完成使命将其置空标志着变量系统的初始化彻底完成4 返回成功returnNGX_OK;}