(转自http://blog.lifeibo.com/blog/2011/12/17/nginx-varibles.html)
nginx中的变量在nginx中的使用非常的多,正因为变量的存在,使得nginx在配置上变得非常灵活。
我们知道,在nginx的配置文件中,配合变量,我们可以动态的得到我们想要的值。最常见的使用是,我们在写access_log
的格式时,需要用到多很多变量。 而这些变量是如何工作的呢?我们可以输出哪些变量?我们又怎么才能输出自己想要的内容呢?当然,我们可能还想知道,如何在我们的模块里面去使用变量,如何添加变量,获取变量的值,以及设置变量的内容?如何使用,以及需要注意些什么?
问题一大堆,那接下来,就让我们一起去一探nginx源码的秘密。
我要讲的内容
- 变量的分类
- 相关结构
- 模块中操作变量的函数
- 变量的实现源码及流程
1. 变量的分类
站在使用者的角度来看,我们在配置文件中可以看到:
- set添加的变量(变量名由用户设定)
- nginx功能模块中添加的变量,如geo模块(变量名由用户设定)
- nginx内建的变量(变量名已由nginx设定好,可以看
ngx_http_core_variables
结构) - 有一定规则的变量,如”
http_host
”等(有相同前缀,表示某一类变量),我们就称为规则变量吧
从这里,也解决我们的问题,在配置access_log
时,我们可以配置哪些变量,是否是用户添加的变量,是否是内建变量在ngx_http_core_variables
中有,其次,是否是规则变量,另外,如果想输出自己的内容,那只能写模块自己添加一个变量了,或者hack nginx在ngx_http_core_variables
中添加一个变量。
从nginx内部实现上来看,变量可分为:
- hash过的变量
- 未hash过的变量,变量有设置
NGX_HTTP_VAR_NOHASH
- 未hash过的变量,但有一定规则的变量,如以这些串开头的:”
http_
”,”sent_http_
”,”upstream_http_
”,”cookie_
”,”arg_
”
我们在模块里面可以通过ngx_http_add_variable
来添加一个变量,在后面的介绍中我们可以看到。而我们添加的变量,最好不要是以这些规则开头的变量,否则就有可能会覆盖掉这些规则的变量。
从变量获取者来看,可以分为索引变量与未索引的变量。
- 索引变量,我们通过
ngx_http_get_variable_index
来获得一个索引变量的索引号。然后可以通过ngx_http_get_indexed_variable
与ngx_http_get_flushed_variable
来获取索引过变量的值。如果要索引某个变量,则只能在配置文件初始化的时候来设置。ngx_http_get_variable_index
不会添加一个真正的变量,在配置文件初始化结束时,会检查该变量的合法性。索引过的变量,将会有缓存等特性(缓存在r->variables
中)。 - 未索引过的变量,则只能通过
ngx_http_get_variable
来获取变量的值。
2. 相关结构
接下来,我们就要开始进入源码的世界了,先看看几个关键结构:
// ngx_variable_value_t即变量的结果,变量的值
typedef struct {
unsigned len:28;
unsigned valid:1; // 当前变量是否合法
unsigned no_cacheable:1; // 当前变量是否可以缓存,缓存过的变量将只会调用一次get_handler函数
unsigned not_found:1;// 变量是否找到
unsigned escape:1;
u_char *data; // 变量的数据
} ngx_variable_value_t;
// 变量本身的信息
struct ngx_http_variable_s {
ngx_str_t name; // 变量的名称
ngx_http_set_variable_pt set_handler; // 变量的设置函数
ngx_http_get_variable_pt get_handler; // 变量的get函数
uintptr_t data; // 传给get与set_handler的值
ngx_uint_t flags; // 变量的标志
ngx_uint_t index; // 如果有索引,则是变量的索引号
};
// 在ngx_http_core_module的配置文件中保存了所使用的变量信息
typedef struct {
ngx_hash_t variables_hash; // 变量的hash表
ngx_array_t variables; // 索引变量的数组
ngx_hash_keys_arrays_t *variables_keys; // 变量的hash数组
} ngx_http_core_main_conf_t;
// 变量在每个请求中的值是不一样的,也就是说变量是请求相关的
// 所以在ngx_http_request_s中有一个变量数组,主要用于缓存当前请求的变量结果
// 从而可以避免一个变量的多次计数,计算过一次的变量就不用再计算了
// 但里面保存的一定是索引变量的值,是否缓存,也要由变量的特性来决定
struct ngx_http_request_s {
ngx_http_variable_value_t *variables;
}
3. 模块中操作变量的函数
那么,在模块中,我们要如何使用一个变量呢?在前面讲分类的时候,我们也提到过了,这里再总结并细说一下: 首先,如果要添加一个变量,我们需要调用ngx_http_add_variable
函数来添加一个变量。添加时需要指明变量的名称就行了。
// name: 即变量的名字
// flags: 如果同一个变量要多次添加,则flags应该设置NGX_HTTP_VAR_CHANGEABLE
// 否则,多次添加将会提示重复
// flags表示可以是:NGX_HTTP_VAR_CHANGEABLE
// NGX_HTTP_VAR_NOCACHEABLE
// NGX_HTTP_VAR_INDEXED
// NGX_HTTP_VAR_NOHASH
ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags);
然后,要获取变量,如果要高效一点,我们可以先将该变量放到索引数组里面,通过ngx_http_get_variable_index
来添加一个变量的索引:
// name: 即nginx支持的任意变量名
// 返回该变量的索引
ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name);
不过,要注意的是,添加的变量必须是nginx支持的已存在的变量。即如果是hash过的变量,则一定是通过ngx_http_add_variable
添加的变量,否则,一定是规则变量,如”http_host
”。当然,在解析配置文件的时候,变量不一定是要先通过ngx_http_add_variable
然后才能获取索引,这个是不需要有顺序保证的。nginx会将在最后配置文件解析完成后,去验证这些索引变量的合法性,在ngx_http_variables_init_vars
函数中可以看到,我们在后面具体再分析。 所以,可以看到,获取索引的操作,一定是要在解析配置文件的过程是进行的, 一旦配置文件解析完成后,索引变量不能再添加。在获取索引号后,我们需要保存该索引号,以便在后面通过索引号来获取变量。
那么,索引变量的获取,可以通过ngx_http_get_indexed_variable
与ngx_http_get_flushed_variable
来获取,两个函数间的区别,我们后面再介绍:
ngx_http_variable_value_t *ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index);
ngx_http_variable_value_t *ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_uint_t index);
而如果没有索引过的变量,则只能通过ngx_http_get_variable
函数来获取了。
// key 由ngx_hash_strlow来计算
ngx_http_variable_value_t *ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key);
可以看到,key是通过ngx_hash_strlow来计算的,所以变量名是没有大小写区分的。
最后,通过获取变量的函数,我们可以看到,变量是与请求相关的,也就是获取的变量都是与当前请求相关的。
4. 变量的实现源码及流程
那接下来,我们就来看看nginx在源码中的实现吧!
初始化:
首先,在数据结构中,我们知道ngx_http_core_main_conf_t
中保存了变量相关的一些信息,我们添加的变量key放在cmcf->variables_keys
中,而cmcf->variables保存变量的索引结构,cmcf->variables_hash
则保存着变量hash过的结构。
ngx_http_add_variable
添加变量的时候,会先放到cmcf->variables_keys
中,然后在解析完后,再生成hash结构体。
那么,ngx_http_core_module
的preconfiguration阶段,调用ngx_http_variables_add_core_vars
初始化变量的数据结构,然后再添加ngx_http_core_variables
结构中的变量。所以可以看出,nginx中内建的变量是在这个数组里面的。 然后在解析其它模块的配置文件时,会通过ngx_http_add_variable
函数来添加变量:
ngx_http_variable_t *
ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags)
{
// 先检查变量是否在已添加
key = cmcf->variables_keys->keys.elts;
for (i = 0; i < cmcf->variables_keys->keys.nelts; i++) {
if (name->len != key[i].key.len
|| ngx_strncasecmp(name->data, key[i].key.data, name->len) != 0)
{
continue;
}
v = key[i].value;
// 如果已添加,并且是不可变的变量,则提示变量的重复添加
// 其它NGX_HTTP_VAR_CHANGEABLE就是为了让变量的重复添加时不出错,都指向同一变量
if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"the duplicate \"%V\" variable", name);
return NULL;
}
// 如果变量已添加,并且有NGX_HTTP_VAR_CHANGEABLE表志,则直接返回
return v;
}
// 添加这个变量
v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t));
v->name.len = name->len;
// 注意,变量名不区分大小写
ngx_strlow(v->name.data, name->data, name->len);
rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0);
if (rc == NGX_ERROR) {
return NULL;
}
return v;
}
在添加完变量后,我们需要设置变量的get_handler
与set_handler
。get_handler
是当我们在获取变量的时候调用的函数,在该函数中,我们需要设置变量的值。而在set_handler
则是用于主动设置变量的值。get_handler
与set_handler
的区别是:get_handler
是在变量使用时获取值,而set_handler
则是变量会主动先设置好,在使用的时候就不用再算了。目前,set
指令,设置一个变量的值是用的set_handler
。 在需要获取变量的模块中,可以通过ngx_http_get_variable_index
来得到变量的索引,这个函数工作很简单,就是在ngx_http_core_main_conf_t
的variables中添加一个变量,并返回该变量在数组中的索引号。源码就不展示了。然后,在解析配置文件之后,在ngx_http_block
中通过ngx_http_variables_init_vars
函数来初始化变量,在ngx_http_variables_init_vars
中,会做两个事情,检查索引变量,以及初始化变量的hash表。首先,对索引数组中的每一个元素,会先检查是否在ngx_http_core_main_conf_t
的variables_keys
中出现,即是否是添加过的,然后再检查是否是有特定规则的变量,如”http_host
”,如果都不是,则说明该变量是不存在的,该索引会对应于一个不存在的变量,所以就会提示错误,程序无法启动。然后,如果变量有设置NGX_HTTP_VAR_NOHASH
,则会跳过该变量,不进行hash,再对hash过的变量建立hash表。
在请求中: 当一个请求过来时,在ngx_http_init_request函数中,即请求初始化的时候,会建立一个与ngx_http_core_main_conf_t中的变量索引数组variables大小一样的数组。r->variables有两个作用,一是为了缓存变量的值,二是可以在创建子请求时,父请求给子请求传递一些信息。注意,变量的值是与当前请求相关的,所以每个请求里面会不一样。 然后在模块里面ngx_http_get_indexed_variable和ngx_http_get_flushed_variable,这两个函数的代码还是要小讲一下:
ngx_http_variable_value_t *
ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index)
{
ngx_http_variable_t *v;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
// 变量已经获取过了,就不再计算变量的值,直接返回
if (r->variables[index].not_found || r->variables[index].valid) {
return &r->variables[index];
}
// 如果变量是初次获取,则调用变量的get_handler来得到变量值,并缓存到r->variables中去
v = cmcf->variables.elts;
if (v[index].get_handler(r, &r->variables[index], v[index].data)
== NGX_OK)
{
if (v[index].flags & NGX_HTTP_VAR_NOCACHEABLE) {
r->variables[index].no_cacheable = 1;
}
return &r->variables[index];
}
// 变量获取失败,设置为不合法,以及未找到
// 注意我们在调用完本函数后,需要检查函数的返回值以及这两个属性
r->variables[index].valid = 0;
r->variables[index].not_found = 1;
return NULL;
}
ngx_http_variable_value_t *
ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_uint_t index)
{
ngx_http_variable_value_t *v;
v = &r->variables[index];
if (v->valid) {
// 变量已经获取过了,而且是合法的并且可缓存的,则直接返回
if (!v->no_cacheable) {
return v;
}
// 否则,清除标志,并再次获取变量的值
v->valid = 0;
v->not_found = 0;
}
return ngx_http_get_indexed_variable(r, index);
}
注意:ngx_http_get_flushed_variable
会考虑到变量的cache标志,如果变量是可缓存的,则只有在变量是合法的时才返回变量的值,否则重新获取变量的值。而ngx_http_get_indexed_variable
则不管变量是否可缓存,只要获取过一次了,不管是否成功,则都不会再获取了。最后,如果是未索引的变量,我们可以通过ngx_http_get_variable
函数来得到变量的值。ngx_http_get_variable
做的工作:
- 变量是hash过的,而且变量有索引过,则调用
ngx_http_get_flushed_variable
来得到变量值。 - 变量hash过,未索引过,则调用变量的
get_handler
来获取变量,注意,此时每次调用变量,都将会调用get_handler
来计算变量的值,然后返回该值。注意因为只有索引过的变量的值才会缓存到ngx_http_request_t
的variables中去,所以变量的添加方要注意,如果当前变量是可缓存的,要将该变量建立索引,即调用完ngx_http_add_variable
后,再调用ngx_http_get_variable_index
来将该变量建立索引。 - 特定规则的变量,”http_”开头的会调用
ngx_http_variable_unknown_header_out
函数,”upstream_http_
”开头的会调用ngx_http_upstream_header_variable
函数,”cookie_”开头的会调用ngx_http_variable_cookie
函数,”arg_”开头的会调用ngx_http_variable_argument
函数。 - 变量未找到,设置变量
至此,变量的整个流程差不多就完了,另外还有一个要注意的是,在创建子请求时候的变量。在ngx_http_subrequest
函数中,我们可以看到,子请求的variables是直接指向父请求的variables数组的,所以子请求与父请求是共享variables数组的,这样父子请求就可以传递变量的值。但正因为如此,我们在使用父子请求的时候会产生一些问题,如果一个父请求创建多个子请求,他们之间获取同一个变量时,会有很明显的干扰,因为每个请求的环境是不一样的,这样获取的值也是不一样的。
好吧,变量也简单的介绍了一下。
相关推荐
### Nginx源码剖析:进程模型、内存管理与请求处理 #### 1.1 Nginx 的进程模型 Nginx 是一款广泛使用的高性能 Web 和反向代理服务器,其核心设计之一是高效的进程模型。Nginx 采用的是 Master-Worker 模型,其中...
通过以上分析,我们可以看出Nginx在设计上注重模块化、可移植性和内存管理的高效性。深入研究Nginx的源代码,能帮助开发者理解如何构建高性能、低延迟的网络服务,并为自定义扩展提供基础。对于想要优化服务器性能或...
标题“nginx源码分析configure脚本详解”表明本文将详细讨论和分析Nginx的源码编译过程中使用的configure脚本,而描述中提到的“需要的朋友可以参考下”,意味着本文的目的在于为需要深入了解Nginx编译机制的读者...
**Nginx模块源码分析:nginx-notice-2** Nginx是一个高性能的Web服务器和反向代理服务器,以其轻量级、高并发、低内存占用等特性在IT行业中广泛应用。开发者通常会通过编写自定义模块来扩展Nginx的功能,以满足特定...
经常需要配置Nginx ,其中有许多以 $ 开头的变量,经常需要...可能是对 Ngixn资源不熟悉,干脆就直接读源码,分析出支持的变量。 Nginx支持的http变量实现在 ngx_http_variables.c 的 ngx_http_core_variables存储实现
Nginx源代码分析是学习使用Nginx源码开发人员的指导丛书,为开发者提供了详细的Nginx源代码分析指导。下面是对Nginx源代码的分析指导。 目录结构 Nginx的源码目录结构层次明确,从自动编译脚本到各级的源码,层次...
三、Nginx源码分析 1. `main.c`:Nginx的主入口,初始化主进程,加载配置文件,创建worker进程。 2. `ngx_cycle_t`结构体:表示Nginx的一个运行周期,包含了整个Nginx运行所需的配置信息和全局变量。 3. `ngx_...
首先,要理解Nginx源码,首先要了解Nginx的基本架构。Nginx采用多进程模型运行,其中包含一个master进程和多个worker进程。master进程的主要作用是管理worker进程,包括处理信号,新建、关闭worker进程,以及升级...
2. **配置路径**:将解压后的二进制文件和配置文件路径添加到系统环境变量,或者将 Nginx 放置在默认路径(如 `/usr/local/nginx`)下。 3. **启动 Nginx**: 在解压后的目录下执行: ``` ./sbin/nginx ``` 4....
安装lua-nginx-module通常涉及编译Nginx源码,并在编译时添加lua-nginx-module模块。配置时,通过`load_module`指令加载模块,然后在合适的上下文中使用`lua`指令插入Lua代码。 3.2 常见指令 - `set_by_lua`: 在...
Nginx与源码分析 了解Nginx的源码有助于我们更好地理解和定制其功能。Nginx采用事件驱动模型,通过多进程或线程来处理请求,这使得它在处理高并发场景时表现出色。源码中包含了许多模块,如http、stream、mail等,...
3. 集成Fastdfs-nginx-module:将解压后的fastdfs-nginx-module-master目录中的源码合并到Nginx的源码树中,然后重新编译Nginx,以便包含该模块。 4. 配置FastDFS和Nginx:配置FastDFS的连接参数,如tracker服务器...
2. **配置环境变量**:将Nginx的安装目录添加到系统环境变量`Path`中,以便在命令行中直接运行`nginx`命令。 3. **启动Nginx**:在Nginx的安装目录下找到`nginx.exe`,双击启动,或者在命令行中运行`nginx`。 4. **...
以下是对Nginx线程池源码分析的关键知识点的详细说明: 1. **任务节点**: 任务节点是线程池处理的基本单位,由`CB_FUN`类型的`handler`和`void *`类型的`argv`组成。`handler`是指向任务执行函数的指针,该函数...
**15.9 Nginx代码分析之(二)——EmptyGif是如何工作的** 深入分析Nginx中用于生成透明GIF图片的代码实现。 **15.10 Nginx连接处理** 解析Nginx如何处理客户端连接,包括连接的建立、维护和关闭等过程。 #### ...