Nginx变量详解

1、源码解析

1.1 变量分类

nginx变量分为两种,一种是nginx内置变量,可以通过官方网站查询到的含义,另外一种则是在nginx配置文件中通过set指令设置的变量,首先来看一下nginx的内置变量实现方式。

1.1.1 nginx内置变量

首先看看定义内置变量的结构体

1
2
3
4
5
6
7
8
struct ngx_http_variable_s {
ngx_str_t name; /* must be first to build the hash */
ngx_http_set_variable_pt set_handler;
ngx_http_get_variable_pt get_handler;
uintptr_t data;
ngx_uint_t flags;
ngx_uint_t index;
};

上面这个结构体中,name代表变量名称,比如$request_uri,$args等,set_handler暂时没有使用到,get_handler获取变量值,data一般表示为变量值来自于这里,flags表示此变量遵循的一些规则,比如NGX_HTTP_VAR_CHANGEABLE,index表示此变量在整个nginx变量系统中的索引。

接下来就是将变量添加到nginx的变量系统中了,这里以http模块的核心变量为例进行说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
ngx_http_variable_t *
ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags)
{
ngx_int_t rc;
ngx_uint_t i;
ngx_hash_key_t *key;
ngx_http_variable_t *v;
ngx_http_core_main_conf_t *cmcf;

if (name->len == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid variable name \"$\"");
return NULL;
}

// 前缀变量,例如http_,arg_ 等
if (flags & NGX_HTTP_VAR_PREFIX) {
return ngx_http_add_prefix_variable(cf, name, flags);
}

cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

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;

if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"the duplicate \"%V\" variable", name);
return NULL;
}

if (!(flags & NGX_HTTP_VAR_WEAK)) {
v->flags &= ~NGX_HTTP_VAR_WEAK;
}

return v;
}

v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t));
if (v == NULL) {
return NULL;
}

v->name.len = name->len;
v->name.data = ngx_pnalloc(cf->pool, name->len);
if (v->name.data == NULL) {
return NULL;
}

ngx_strlow(v->name.data, name->data, name->len);

v->set_handler = NULL;
v->get_handler = NULL;
v->data = 0;
v->flags = flags;
v->index = 0;

rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0);

if (rc == NGX_ERROR) {
return NULL;
}

if (rc == NGX_BUSY) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"conflicting variable name \"%V\"", name);
return NULL;
}

return v;
}

这个函数的作用就是将定义的变量添加到http核心配置中,为获取变量的值做好准备,其他变量类似。接下来就是如何获取变量值了,http的日志模块提供了一个很好的范本。
接下来看看ngx_http_variables_init_vars函数的实现,这个函数的主要功能是设置变量的get_handler,从各个模块初始化的变量key中寻找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
ngx_int_t
ngx_http_variables_init_vars(ngx_conf_t *cf)
{
size_t len;
ngx_uint_t i, n;
ngx_hash_key_t *key;
ngx_hash_init_t hash;
ngx_http_variable_t *v, *av, *pv;
ngx_http_core_main_conf_t *cmcf;

/* set the handlers for the indexed http variables */

cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

v = cmcf->variables.elts;
pv = cmcf->prefix_variables.elts;
key = cmcf->variables_keys->keys.elts;

// cmcf->variable变量是配置文件中使用的变量
for (i = 0; i < cmcf->variables.nelts; i++) {

// 这个key是所有模块定义的变量key
for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) {

av = key[n].value;

if (v[i].name.len == key[n].key.len
&& ngx_strncmp(v[i].name.data, key[n].key.data, v[i].name.len)
== 0)
{
// 设置get_handler
v[i].get_handler = av->get_handler;
v[i].data = av->data;

av->flags |= NGX_HTTP_VAR_INDEXED;
v[i].flags = av->flags;

av->index = i;

if (av->get_handler == NULL
|| (av->flags & NGX_HTTP_VAR_WEAK))
{
break;
}

goto next;
}
}

len = 0;
av = NULL;

// 设置前缀变量get_handler
for (n = 0; n < cmcf->prefix_variables.nelts; n++) {
if (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)
{
av = &pv[n];
len = pv[n].name.len;
}
}

if (av) {
v[i].get_handler = av->get_handler;
v[i].data = (uintptr_t) &v[i].name;
v[i].flags = av->flags;

goto next;
}

// 如果变量get_handler为NULL,报错
if (v[i].get_handler == NULL) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"unknown \"%V\" variable", &v[i].name);

return NGX_ERROR;
}

next:
continue;
}

// 如果带有nohash的标识,则将key.data置为NULL,在ngx_hash_init就不会将该key放在hash桶里面,比如proxy模块的几个变量
for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) {
av = key[n].value;

if (av->flags & NGX_HTTP_VAR_NOHASH) {
key[n].key.data = NULL;
}
}


hash.hash = &cmcf->variables_hash;
hash.key = ngx_hash_key;
hash.max_size = cmcf->variables_hash_max_size;
hash.bucket_size = cmcf->variables_hash_bucket_size;
hash.name = "variables_hash";
hash.pool = cf->pool;
hash.temp_pool = NULL;

// 开始初始化变量hash表
if (ngx_hash_init(&hash, cmcf->variables_keys->keys.elts,
cmcf->variables_keys->keys.nelts)
!= NGX_OK)
{
return NGX_ERROR;
}

cmcf->variables_keys = NULL;

return NGX_OK;
}


http模块如何获取变量,接下来看看ngx_http_get_variable函数实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
ngx_http_variable_value_t *
ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key)
{
size_t len;
ngx_uint_t i, n;
ngx_http_variable_t *v;
ngx_http_variable_value_t *vv;
ngx_http_core_main_conf_t *cmcf;

cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
// 从全局变量hash表中根据key找到对应变量
v = ngx_hash_find(&cmcf->variables_hash, key, name->data, name->len);

if (v) {
if (v->flags & NGX_HTTP_VAR_INDEXED) {
return ngx_http_get_flushed_variable(r, v->index);
}

if (ngx_http_variable_depth == 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"cycle while evaluating variable \"%V\"", name);
return NULL;
}

ngx_http_variable_depth--;

vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));

if (vv && v->get_handler(r, vv, v->data) == NGX_OK) {
ngx_http_variable_depth++;
return vv;
}

ngx_http_variable_depth++;
return NULL;
}

vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));
if (vv == NULL) {
return NULL;
}

len = 0;

v = cmcf->prefix_variables.elts;
n = cmcf->prefix_variables.nelts;

// 从前缀变量中查找
for (i = 0; i < cmcf->prefix_variables.nelts; i++) {
if (name->len >= v[i].name.len && name->len > len
&& ngx_strncmp(name->data, v[i].name.data, v[i].name.len) == 0)
{
len = v[i].name.len;
n = i;
}
}

// 从前缀变量中找到该变量
if (n != cmcf->prefix_variables.nelts) {
if (v[n].get_handler(r, vv, (uintptr_t) name) == NGX_OK) {
return vv;
}

return NULL;
}

vv->not_found = 1;

return vv;
}

1.1.2 配置文件变量

配置文件变量是指set指令设置的变量,

1
set $a value;

具体可以参考set

首先来看看set指令解析函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
static char *
ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_rewrite_loc_conf_t *lcf = conf;

ngx_int_t index;
ngx_str_t *value;
ngx_http_variable_t *v;
ngx_http_script_var_code_t *vcode;
ngx_http_script_var_handler_code_t *vhcode;

value = cf->args->elts;

// 如果没有带$符号,则返回错误
if (value[1].data[0] != '$') {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid variable name \"%V\"", &value[1]);
return NGX_CONF_ERROR;
}

value[1].len--;
value[1].data++;

// 将变量添加到全局变量中
v = ngx_http_add_variable(cf, &value[1],
NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_WEAK);
if (v == NULL) {
return NGX_CONF_ERROR;
}

// 获取变量索引
index = ngx_http_get_variable_index(cf, &value[1]);
if (index == NGX_ERROR) {
return NGX_CONF_ERROR;
}

if (v->get_handler == NULL) {
v->get_handler = ngx_http_rewrite_var;
v->data = index;
}

if (ngx_http_rewrite_value(cf, lcf, &value[2]) != NGX_CONF_OK) {
return NGX_CONF_ERROR;
}

//处理set_handler
if (v->set_handler) {
vhcode = ngx_http_script_start_code(cf->pool, &lcf->codes,
sizeof(ngx_http_script_var_handler_code_t));
if (vhcode == NULL) {
return NGX_CONF_ERROR;
}

vhcode->code = ngx_http_script_var_set_handler_code;
vhcode->handler = v->set_handler;
vhcode->data = v->data;

return NGX_CONF_OK;
}

vcode = ngx_http_script_start_code(cf->pool, &lcf->codes,
sizeof(ngx_http_script_var_code_t));
if (vcode == NULL) {
return NGX_CONF_ERROR;
}

vcode->code = ngx_http_script_set_var_code;
vcode->index = (uintptr_t) index;

return NGX_CONF_OK;
}

这个函数首先解析出第一个变量的名字,并将其加入到nginx的变量系统中,其次解析作为第一个值的变量(第二个变量),但是第二个变量可能是一个普通的字符串也有可能是多个变量组成的字符串,都是在ngx_http_rewrite_value函数中进行处理,接下来看看ngx_http_rewrite_value函数实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

static char *
ngx_http_rewrite_value(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf,
ngx_str_t *value)
{
ngx_int_t n;
ngx_http_script_compile_t sc;
ngx_http_script_value_code_t *val;
ngx_http_script_complex_value_code_t *complex;

n = ngx_http_script_variables_count(value);

if (n == 0) {
val = ngx_http_script_start_code(cf->pool, &lcf->codes,
sizeof(ngx_http_script_value_code_t));
if (val == NULL) {
return NGX_CONF_ERROR;
}

n = ngx_atoi(value->data, value->len);

if (n == NGX_ERROR) {
n = 0;
}

val->code = ngx_http_script_value_code;
val->value = (uintptr_t) n;
val->text_len = (uintptr_t) value->len;
val->text_data = (uintptr_t) value->data;

return NGX_CONF_OK;
}
//带有$的变量,value也是变量,如set $aa $bb,这里的$bb就是变量,
complex = ngx_http_script_start_code(cf->pool, &lcf->codes,
sizeof(ngx_http_script_complex_value_code_t));
if (complex == NULL) {
return NGX_CONF_ERROR;
}

complex->code = ngx_http_script_complex_value_code;
complex->lengths = NULL;

ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));

sc.cf = cf;
sc.source = value;
sc.lengths = &complex->lengths;
sc.values = &lcf->codes;
sc.variables = n;
sc.complete_lengths = 1;

if (ngx_http_script_compile(&sc) != NGX_OK) {
return NGX_CONF_ERROR;
}

return NGX_CONF_OK;
}

这个函数中,n为0表示value不包含变量,为纯字符串,否则表示value中包含变量,采用复杂变量解析方式。

2、总结

以上就是Nginx变量的相关解释,Nginx变量在nginx.conf配置文件中有着至关重要的作用,可定制性强。