Nginx 变量和脚本引擎实现原理
变量使用场景
nginx.conf 配置文件中使用美元符号 $
定义和使用变量,如下示例:
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $server_name "$http_user_agent"';
set $name ${host}abc;
本文讲述以上两个示例中两种变量的工作原理,并且该工作原理基于 nginx 作为 HTTP 服务器的使用场景。
内置变量实现原理
内置变量为 nginx 内部实现的变量,所有内置变量可参考 nginx 官方文档:
http://nginx.org/en/docs/varindex.html。
以上示例中log_format main
中使用的变量均为内置变量。
变量类型
内置变量分为两种,一种可以在 nginx 的配置文件中使用的变量,一种不可以在 nginx 的配置文件中使用,即 nginx 模块定义且仅在模块之间使用的变量。
可以在 nginx 的配置文件中使用的变量又可分为两种,一种是名称固定的内置变量,如 $remote_addr
,另一种是前缀变量,如获取请求头部的 $http_
变量,$http_user_agent
,表示请求头域 User-Agent
的头域值。
其中 HTTP 前缀变量可分为五种,说明如下:
变量前缀 |
说明 |
示例 |
arg_ | 用户请求 url 参数 | $arg_user |
http_ | 用户请求头域 | $http_x_real_ip |
send_http_ | nginx 响应头域 | $send_http_server |
cookie_ | 请求 cookie 中的某个值 | $cookie_sessionid |
upstream_http_ | 上游服务器响应头域 | $upstream_http_date |
内部变量分类图示如下:
原理说明
nginx 中的每个模块定义自己的内置变量,包括变量名、获得变量值的函数(get_handler)、设置变量值的函数(set_handler)等
一般在 nginx 进行配置解析之前(preconfiguration)将当前模块的所有内置变量根据变量类型添加到全局数组中,如将前缀变量添加到全局数组
prefix_variables
中;名称固定的变量添加到全局数组variables
中,并将其保存在全局哈希数组variables_keys
中在所有 HTTP 模块配置解析完成之后,根据全局哈希数组
variables_keys
构造全局哈希表variables_hash
(此时哈希数组 variables_keys 就功成身退了,后面不会再使用)当每个 HTTP 请求到来时,nginx 都会创建一个
ngx_http_request_t *r, r->variables
数组,该数组下标和上述步骤2中的全局数组variables
下标是一一对应的。这样在获得变量值之后,可以将变量的值缓存在该数组中,下次获得相同变量的值时,可以直接从该请求级别的数组中获得
根据以上数据结构,可以看到若要获得一个变量取值,有两种方法:
1) 根据变量名称通过全局哈希表 variables_hash
查找
2) 根据变量索引通过全局数组 variables
查找
Q&A
如何获得变量的索引呢?
nginx 内部提供可以根据变量名称获得变量索引的函数 ngx_http_get_variable_index(对于仅在 nginx 模块之间使用,不对外提供的变量,通常不会将变量保存在全局哈希表中,只能通过索引的方式获得变量值,可以使用该函数获得变量索引,然后利用索引获得变量值)。
内置变量是否进行惰性求值?
是惰性求值。变量只有在使用时才会进行求值,而不是 HTTP 请求创建时,对所有变量进行求值。
内置变量值如何进行缓存?
当获得内置变量值之后,会将变量取值保存在ngx_http_request_t *r
的 variables
数组中,这样下次想要再次获得相应变量值时,会首先从ngx_http_request_t *r
的 variables
数组中索引变量的值,提高变量求值的执行效率。
内存布局
内存布局如下:
源码索引
模块定义的变量散落在模块文件中,如 ngx_http_realip_module 模块添加变量的函数为
ngx_http_realip_module.c/ngx_http_realip_add_variables
nginx_http_core_module 模块会被首先调用,它的内置变量源码位置为
ngx_http_core_module.c/ngx_http_core_preconfiguration
ngx_http_variables.c/ngx_http_get_variable_index
函数根据变量名获得变量在全局变量variables
中的索引ngx_http_variables.c/ngx_http_get_indexed_variable
函数根据变量索引从全局数组variable
中获得变量值ngx_http_variables.c/ngx_http_get_variable
函数根据变量名从全局哈希表variables_hash
中获得变量值
外部变量实现原理
外部变量是指通过 ngx_http_rewrite_module 模块中提供的 set
指令定义的变量,由用户在 nginx 配置文件中设置。set
指令使用方法官方文档:
http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#set
更为详细说明,可参考春哥(agentzh 章亦春)的文章:
https://openresty.org/download/agentzh-nginx-tutorials-en.html
原理说明
这里以 set $name ${host}abc;
为例,讲述自定义变量的实现原理。
将变量名
name
作为 key,变量值相关信息作为 value,保存在全局哈希数组variables_keys
中将变量名
name
保存在全局数组variables
中编译变量值:将获得变量值相关的函数保存在栈/数组中,数据结构如下:
4. 执行变量值:当需要使用变量 name
时,执行栈中的函数,获得变量取值
1) 调用左边数组中的元素0,该函数的实现为依次调用右边函数栈中所有函数,获得变量值的总长度,并且申请内存,用于保存变量值
2) 依次调用左边函数栈的元素1和元素2,获得变量取值,保存在步骤1)申请的内存中
3) 调用左边函数栈的元素3,将变量值缓存在
ngx_http_request_t *r
的 variables
数组中
Q&A
外部变量是否进行惰性求值?
以上步骤1.2.3在 nginx 启动时执行,步骤4在每个请求处理时,且执行到对应的配置上下文(对应配置文件中的 server、location)时才会对变量进行求值,可以将其理解为惰性求值。
执行的变量值如何缓存?
步骤4在求值时,首先从ngx_http_request_t *r
的 variables
数组中索引变量的值,如果无值,则进行变量取值,并将变量取值缓存在ngx_http_request_t *r
的 variables
数组中。
源码索引
ngx_http_rewrite_module.c/ngx_http_rewrite_set
函数在nginx 解析配置文件,遇到set
指令时执行,该函数功能对应上述步骤1.2.3当执行请求时,在 rewrite 阶段调用 ngx_http_rewrite_module 模块的句柄
ngx_http_rewrite_handler
调用当前请求上下文的函数栈(上述步骤3中初始化的函数数组),对变量进行求值
往期文章