leevis.com
leevis.com copied to clipboard
nginx 的变量
概述
nginx的变量为nginx配置添加了很多的灵活性。变量也在nginx中随处可见。例如日志的格式就使用了变量。
变量的添加
在nginx http模块中,每个ctx的preconfiguration基本都是用来添加变量了。nginx中的变量有三种添加方式, 其一是已经有的变量,如:‘$binary_remote_addr’,二是配置文件生成的变量,如:‘set $name "liwq";’ 三是通过正则表达式捕获来生成变量,例如:server_name ~^(www.)?(?<domain>.+)$; 通过$domain 就可以访问这个捕获的变量了。
我们先来看下第一种变量是如何添加的? 在ngx_http_core_module_ctx的ngx_http_core_preconfiguration中,调用了ngx_http_variables_add_core_vars函数。该函数循环一个变量数组,调用ngx_hash_add_key把变量添加到cmcf->variables_keys中,前缀变量($http_xxx)会添加到cmcf->prefix_variables中。
第二种是直接调用ngx_http_get_variable_index函数,该函数的含义是直接向cmcf->variables数组中添加一个类型为ngx_http_variable_t的变量,直接返回数组下标。取值的时候直接调用ngx_http_get_indexed_variable函数。
第三种是正则编译变量,调用ngx_http_regex_compile 函数。
添加变量代码分析
通过分析代码来看下变量是如何被添加的。
- 已有的变量
ngx_http_core_module模块提供了大部分的变量,主要是请求头相关的。 调用栈:ngx_http_core_preconfiguration->ngx_http_variables_add_core_vars->ngx_http_add_variable
在ngx_http_variables_add_core_vars函数中,先调用了ngx_hash_keys_array_init函数初始化了添加变量所需要的结构体。参考《hash表源码分析》
在ngx_http_variables_add_core_vars函数中循环调用ngx_http_add_variable函数,该函数又调用ngx_hash_add_key函数把变量添加到cmcf->variables_keys->keys 这个数组中。参考《hash表源码分析》。 前缀变量则调用ngx_http_add_prefix_variable函数把变量添加到cmcf->prefix_variables数组中。
- 配置文件中set的变量
set命令在ngx_http_rewrite_module提供。该模块请参考《ngx_http_rewrite_module代码分析》。
set命令由ngx_http_rewrite_set函数处理,首先调用ngx_http_add_variable添加变量(在ngx启动过程中先处理配置文件的set命令,再调用http模块的postconfiguration方法添加变量)。 ngx_http_add_variable调用方式:
v = ngx_http_add_variable(cf, &value[1],
NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_WEAK); // flag表示:可以修改的|虚弱的(虚弱的对命令变量的标识)
然后会调用ngx_http_get_variable_index函数,在cmcf->variables中也加入一个变量,类型为ngx_http_variable_t。并且把这个元素在cmcf->variables数组的下标保存到cmcf->variables_keys->keys 这个数组元素的data上。
- 正则表达式捕获生成的变量
正则表达式捕获生成的变量,如果没有添加别名,则可以用$1 $2 这样来饮用,添加了别名的就可用通过别名饮用,如上述例子中的$domain。 通过调用ngx_regex_compile函数编译正则表达式,正则表达式请参考《pcre 库在nginx中的应用》,然后循环捕获的别名,调用ngx_http_add_variable把别名添加到变量里。 并调用ngx_http_get_variable_index把别名同时添加到cmcf->variables数组中。
// 变量的结构
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;
};
// 变量的返回的值
typedef struct {
unsigned len:28;
unsigned valid:1;
unsigned no_cacheable:1;
unsigned not_found:1;
unsigned escape:1;
u_char *data;
} ngx_variable_value_t;
// set 指令添加变量会调用该函数
// http模块的postconfiguration函数会调用该函数
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_xxx $cookie_xxx等
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;
}
// 去除weak标志,weak是在配置文件通过set指令设置的变量
v->flags &= 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;
}
ngx_int_t
ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name)
{
ngx_uint_t i;
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 NGX_ERROR;
}
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
v = cmcf->variables.elts;
if (v == NULL) {
if (ngx_array_init(&cmcf->variables, cf->pool, 4,
sizeof(ngx_http_variable_t))
!= NGX_OK)
{
return NGX_ERROR;
}
} else {
for (i = 0; i < cmcf->variables.nelts; i++) {
if (name->len != v[i].name.len
|| ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0)
{
continue;
}
return i;
}
}
v = ngx_array_push(&cmcf->variables);
if (v == NULL) {
return NGX_ERROR;
}
v->name.len = name->len;
v->name.data = ngx_pnalloc(cf->pool, name->len);
if (v->name.data == NULL) {
return NGX_ERROR;
}
ngx_strlow(v->name.data, name->data, name->len);
v->set_handler = NULL;
v->get_handler = NULL;
v->data = 0;
v->flags = 0;
v->index = cmcf->variables.nelts - 1;
return v->index;
}
预处理
在ngx_http_block
调用了 ngx_http_variables_init_vars
初始化变量,主要就是检测有没有引用不存在的变量、初始化这个哈希表variables_hash
加快查找。
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;
// 引用变量 和 变量对应
for (i = 0; i < cmcf->variables.nelts; i++) {
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)
{
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;
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;
}
// 引用了不存在的变量
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;
}
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;
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;
}
变量的使用
变量最终的获取是通过调用下面3个函数:
- ngx_http_get_variable
- ngx_http_get_flushed_variable
- ngx_http_get_indexed_variable
一般是先用该函数ngx_http_compile_complex_value编译,使用的时候调用ngx_http_complex_value函数。
编译的时候定义一个ngx_http_compile_complex_value_t类型的结构体,其中*value指向变量字符串,*complex_value指向编译结果,其类型为:ngx_http_complex_value_t。 定义如下:
typedef struct {
ngx_str_t value;
ngx_uint_t *flushes;
void *lengths;
void *values;
} ngx_http_complex_value_t;
typedef struct {
ngx_conf_t *cf;
ngx_str_t *value;
ngx_http_complex_value_t *complex_value;
unsigned zero:1;
unsigned conf_prefix:1;
unsigned root_prefix:1;
} ngx_http_compile_complex_value_t;
// 编译变量,添加处理回调函数。
ngx_int_t
ngx_http_compile_complex_value(ngx_http_compile_complex_value_t *ccv)
{
ngx_str_t *v;
ngx_uint_t i, n, nv, nc;
ngx_array_t flushes, lengths, values, *pf, *pl, *pv;
ngx_http_script_compile_t sc;
// eg: $binary_remote_addr$host
v = ccv->value;
nv = 0;
nc = 0;
// 统计变量的个数
for (i = 0; i < v->len; i++) {
if (v->data[i] == '$') {
if (v->data[i + 1] >= '1' && v->data[i + 1] <= '9') {
nc++; // 正则表达式补获变量的个数
} else {
// 普通变量的个数
nv++;
}
}
}
if ((v->len == 0 || v->data[0] != '$')
&& (ccv->conf_prefix || ccv->root_prefix))
{
if (ngx_conf_full_name(ccv->cf->cycle, v, ccv->conf_prefix) != NGX_OK) {
return NGX_ERROR;
}
ccv->conf_prefix = 0;
ccv->root_prefix = 0;
}
ccv->complex_value->value = *v;
ccv->complex_value->flushes = NULL;
ccv->complex_value->lengths = NULL;
ccv->complex_value->values = NULL;
if (nv == 0 && nc == 0) {
return NGX_OK;
}
n = nv + 1;
if (ngx_array_init(&flushes, ccv->cf->pool, n, sizeof(ngx_uint_t))
!= NGX_OK)
{
return NGX_ERROR;
}
// nv 是变量个数,2倍是需要处理变量结果的长度和值。
n = nv * (2 * sizeof(ngx_http_script_copy_code_t)
+ sizeof(ngx_http_script_var_code_t))
+ sizeof(uintptr_t);
if (ngx_array_init(&lengths, ccv->cf->pool, n, 1) != NGX_OK) {
return NGX_ERROR;
}
n = (nv * (2 * sizeof(ngx_http_script_copy_code_t)
+ sizeof(ngx_http_script_var_code_t))
+ sizeof(uintptr_t)
+ v->len
+ sizeof(uintptr_t) - 1)
& ~(sizeof(uintptr_t) - 1);
if (ngx_array_init(&values, ccv->cf->pool, n, 1) != NGX_OK) {
return NGX_ERROR;
}
// 保存变量数组下标
pf = &flushes;
// 保存处理变量结果长度的函数
pl = &lengths;
// 保存处理变量结果值的函数
pv = &values;
ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
sc.cf = ccv->cf;
sc.source = v;
sc.flushes = &pf;
sc.lengths = &pl;
sc.values = &pv;
sc.complete_lengths = 1;
sc.complete_values = 1;
sc.zero = ccv->zero;
sc.conf_prefix = ccv->conf_prefix;
sc.root_prefix = ccv->root_prefix;
// 添加处理变量的回调函数
if (ngx_http_script_compile(&sc) != NGX_OK) {
return NGX_ERROR;
}
if (flushes.nelts) {
ccv->complex_value->flushes = flushes.elts;
ccv->complex_value->flushes[flushes.nelts] = (ngx_uint_t) -1;
}
ccv->complex_value->lengths = lengths.elts;
ccv->complex_value->values = values.elts;
return NGX_OK;
}
ngx_int_t
ngx_http_script_compile(ngx_http_script_compile_t *sc)
{
u_char ch;
ngx_str_t name;
ngx_uint_t i, bracket;
if (ngx_http_script_init_arrays(sc) != NGX_OK) {
return NGX_ERROR;
}
// eg: $binary_remote_addr$hostname
for (i = 0; i < sc->source->len; /* void */ ) {
name.len = 0;
if (sc->source->data[i] == '$') {
if (++i == sc->source->len) {
goto invalid_variable;
}
......
if (sc->source->data[i] == '{') {
bracket = 1;
if (++i == sc->source->len) {
goto invalid_variable;
}
name.data = &sc->source->data[i];
} else {
bracket = 0;
name.data = &sc->source->data[i];
}
for ( /* void */ ; i < sc->source->len; i++, name.len++) {
ch = sc->source->data[i];
if (ch == '}' && bracket) {
i++;
bracket = 0;
break;
}
if ((ch >= 'A' && ch <= 'Z')
|| (ch >= 'a' && ch <= 'z')
|| (ch >= '0' && ch <= '9')
|| ch == '_')
{
continue;
}
break;
}
if (bracket) {
ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0,
"the closing bracket in \"%V\" "
"variable is missing", &name);
return NGX_ERROR;
}
if (name.len == 0) {
goto invalid_variable;
}
// 变量个数
sc->variables++;
// name 是变量名称,不包括$
// flushes 保存变量的index。
// lengths 保存了变量的长度,处理的回调函数ngx_http_script_copy_var_len_code
// values 保存了变量的值,处理的回调函数ngx_http_script_copy_var_code
if (ngx_http_script_add_var_code(sc, &name) != NGX_OK) {
return NGX_ERROR;
}
continue;
}
......
}
return ngx_http_script_done(sc);
......
}
typedef struct {
u_char *ip;
u_char *pos;
ngx_http_variable_value_t *sp;
ngx_str_t buf;
ngx_str_t line;
/* the start of the rewritten arguments */
u_char *args;
unsigned flushed:1;
unsigned skip:1;
unsigned quote:1;
unsigned is_args:1;
unsigned log:1;
ngx_int_t status;
ngx_http_request_t *request;
} ngx_http_script_engine_t;
// 获取变量的值
ngx_int_t
ngx_http_complex_value(ngx_http_request_t *r, ngx_http_complex_value_t *val,
ngx_str_t *value)
{
size_t len;
ngx_http_script_code_pt code;
ngx_http_script_len_code_pt lcode;
ngx_http_script_engine_t e;
if (val->lengths == NULL) {
*value = val->value;
return NGX_OK;
}
// 不可以缓存的变量需要每次都重新获取
// 清除不可以缓存的变量的上次处理结果
ngx_http_script_flush_complex_value(r, val);
// e 可以看做是个游标,用来执行编译变量添加的回调函数
ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
// lengths 是结果长度
e.ip = val->lengths;
e.request = r;
e.flushed = 1;
len = 0;
while (*(uintptr_t *) e.ip) {
lcode = *(ngx_http_script_len_code_pt *) e.ip;
// 本例中编译时添加的回调函数是ngx_http_script_copy_var_len_code
// 该回调函数获取变量对应的值时,
// 如果需要刷新结果,最终会调用添加变量设置的回调函数get_handler。
// 如果不需要刷新结果,会调用r->variables保存的结果
len += lcode(&e);
}
value->len = len;
value->data = ngx_pnalloc(r->pool, len);
if (value->data == NULL) {
return NGX_ERROR;
}
e.ip = val->values;
e.pos = value->data;
e.buf = *value;
while (*(uintptr_t *) e.ip) {
code = *(ngx_http_script_code_pt *) e.ip;
code((ngx_http_script_engine_t *) &e);
}
*value = e.buf;
return NGX_OK;
}
总结
源码实现上有两种不同,一种是解析请求(例如:$host ...)另一种是在请求执行过程中生成的变量(set指令,server_name指令的正则捕获)。
初始化过程中这两种实现也不同解析请求得到的变量只需要调用ngx_http_add_variable
添加到cmcf->variables_keys
。而请求执行过程中生成的变量则还需要调用ngx_http_get_variable_index
添加到cmcf->variables
中,严格来说是引用的变量会加到cmcf->variables
中。
最终的变量结构体都是ngx_http_variable_t
。