变量的类型
变量有两种类型,分别是普通类型和复杂类型(complex variable)
普通类型变量
普通的变量还有细化的类型,通过不同的宏组合实现不同的功能。
ngx_http_add_variable 和 ngx_http_get_variable可以认为是变量的添加和变量的value获取的主入口。
宏 作用 NGX_HTTP_VAR_CHANGEABLE 此变量能重复定义,也可以使用set的指令修改 NGX_HTTP_VAR_NOCACHEABLE 每次都得使用get_handler获取值,不能使用缓存 NGX_HTTP_VAR_PREFIX 变量是一个前缀,比如$arg_这样的变量(用户获取请求的query参数) NGX_HTTP_VAR_WEAK 通过set修改或者新建的变量,都是属于WEAK类型的变量。 NGX_HTTP_VAR_NOHASH 只能通过索引访问,不能通过变量的名字访问 NGX_HTTP_VAR_INDEXED 变量可以通过索引快速从数组中获取(存在于cmcf->variables中)cmcf->variables 和 cmcf->variables_hash 整个配置中,维护变量的两个数据结构。其中cmcf->variables是一个数组,里面记录了每个变量的get_handler、set_handler和flags。假设一个场景,我们需要根据变量的名字快速的从这个数组中获取变量的get_handler,在没有cmcf->variables_hash的时候,我们需要遍历这个cmcf->variables数组,这样效率会很低。因此需要由一个hash表格,记录每个变量(key)对应在cmcf->variables 的索引。
如果变量是前缀类型的,那么在cmcf->variables_keys中不存在的,而是在cmcf->prefix_variables中维护(并且没有快速的hash索引)
NGX_HTTP_VAR_INDEXED
在http block解析的时候,ngx_http_variables_init_vars函数会给cmcf->variables_hash中的av设置一个索引。
av->flags |= NGX_HTTP_VAR_INDEXED;
前缀变量肯定不是NGX_HTTP_VAR_INDEXED类型的,因为前缀变量cmcf->prefix_variables中维护的。
NGX_HTTP_VAR_PREFIX
前缀类型的变量,ngx_http_add_variable添加变量的时候会调用ngx_http_add_prefix_variable把前缀变量放到cmcf->prefix_variables。
NGX_HTTP_VAR_NOCACHEABLE
所谓的Cache其实是一个优化的手段。如果变量设置了NGX_HTTP_VAR_NOCACHEABLE,那么每次获取变量的值都需要调用get_handler获取。默认的变量(没有强制设置NGX_HTTP_VAR_NOCACHEABLE)都在Cache中。那么Cache是什么呢?Cache其实是请求r生命周期中的variables数组里面专门针对Indexed类型变量优化的字段,如果r->variables中存在,那么就不需要再调用get_handler了。
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 || v->not_found) {
if (!v->no_cacheable) { // cache的变量,直接返回value
return v;
}
v->valid = 0;
v->not_found = 0;
}
return ngx_http_get_indexed_variable(r, index);
}
NGX_HTTP_VAR_CHANGEABLE
如果变量没有这个标志,那么变量不能在配置文件中使用set或者其他模块中重复定义。比如$upstream_response_length这个变量,就不能使用set 设置,否则就会报错
nginx: [emerg] the duplicate "upstream_response_length" variable in xxxxx
NGX_HTTP_VAR_WEAK
目前Nginx中,只有set指令覆盖或者新建的变量,才会设置为WEAK。有很多的文章说NGX_HTTP_VAR_WEAK是不会使用get_handler获取值。其实这里只说对了一半,原因是因为set的机制本来就会覆盖原始的获取变量的方法,即使有get_handler,也不会使用get_handler获取了(因为set指令的求值会优先于get_handler,具体看Nginx的set指令)
NGX_HTTP_VAR_NOHASH
字面的意思就是,这个变量只能通过索引获取,不能通过变量的名字获取(不能通过cmcf->variables_hash查询到key的index)。也就是说,这个变量只能遍历数组,一个个比较后才能获取到。
NGX_HTTP_VAR_NOHASH
类型的变量是不会被添加到哈希表中的。这种类型的变量通常是动态生成的,或者是不需要快速查找的。例如,一些只在特定上下文中使用的变量,或者一些只在配置解析阶段使用的变量,可能就会被标记为 NGX_HTTP_VAR_NOHASH
。
NGX_HTTP_VAR_NOHASH类型的变量,在openresty中,通过ngx.var.xxx(xxx是变量的名字)是获取不到的,因为openresty的这个接口仅仅支持通过变量的名字获取到变量的值。但是在日志模块中,log_format中是能够获取到NOHASH变量的,因此日志模块在compile的时候,就获取index,日志在真实输出的时候就可以拿到了。
以HTTP的upstream模块为例
upstream模块执行preconfiguration阶段的时候,就会调用ngx_http_upstream_add_variables为cf(cf可以理解为整个Nginx的配置的指针)添加upstream模块所有的变量。最终还是会调用ngx_http_add_variable添加变量,ngx_http_upstream_add_variables添加变量的模式,是所有HTTP模块的统一方案,若有第三方模块需要添加变量参考这个方式即可。
static ngx_int_t
ngx_http_upstream_add_variables(ngx_conf_t *cf)
{
ngx_http_variable_t *var, *v;
for (v = ngx_http_upstream_vars; v->name.len; v++) {
var = ngx_http_add_variable(cf, &v->name, v->flags);
if (var == NULL) {
return NGX_ERROR;
}
var->get_handler = v->get_handler;
var->data = v->data;
}
return NGX_OK;
}
复杂(complex)类型变量
复杂类型的官方文档是这样定义的
A complex value, despite its name, provides an easy way to evaluate expressions which can contain text, variables, and their combination
也就是说,复杂类型可以是变量和text的集合,比如${status}_example${upstream_reponse_time}
特性如下:
- 复杂值通常是通过配置指令如
set
,map
,if
等构建的,它们可以包含普通变量、文本和特殊字符的组合。 - 它们可以在运行时根据需要进行计算和构建。
- 访问或计算复杂值的开销通常比普通变量大,因为它们可能需要在每次请求时动态生成。
- 复杂值可以用来创建新的变量,或者在 Nginx 的
rewrite
指令等操作中使用。
但是其使用方式很简单(不考虑实现细节),只需要两个接口就可以。一是编译complex variable类型(ngx_http_compile_complex_value),二是complex variable求值(ngx_http_complex_value)。
以access_log指令为例子,access_log可以配置if=xxxx来实现,根据某个条件被满足才输出access log的日志。比如access_log /var/log/nginx/error.log combined if=$loggable_user_agent;中,loggable_user_agent会被编译为复杂类型。
complex variable编译
if (ngx_strncmp(value[i].data, "if=", 3) == 0) {
s.len = value[i].len - 3;
s.data = value[i].data + 3;
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &s;
ccv.complex_value = ngx_palloc(cf->pool,
sizeof(ngx_http_complex_value_t));
if (ccv.complex_value == NULL) {
return NGX_CONF_ERROR;
}
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}
log->filter = ccv.complex_value;
continue;
}
complex variable 求值
if (log[l].filter) {
if (ngx_http_complex_value(r, log[l].filter, &val) != NGX_OK) {
return NGX_ERROR;
}
if (val.len == 0 || (val.len == 1 && val.data[0] == '0')) {
continue;
}
}
求值后,就可以使用val去执行其他的逻辑了。可以看出access_log的逻辑,只有当if后面的变量的值为0的时候,日志才不打印,其他的时候都是要打印日志的。
Nginx 的set指令
set指令可以重新定义一个变量,或者修改一个变量。比如
set $arg_foo "hhhh";
那么变量$arg_foo的值,会在请求的rewrite阶段进行求值,具体的调用路径为
最终是在ngx_http_script_set_var_code完成对变量$arg_foo的赋值。那么就有疑问了,赋值的来源是什么呢?是在set指令解析的时候,就已经确定了。在ngx_http_rewrite_set里面,有一个流程,就是给变量分配一个index
其中vcode就是ngx_http_script_set_var_code执行时候的code。这里就可以解释为什么r->variables数组里面就已经可以获取到$arg_foo
最后,假设我们没有使用set的指令配置$arg_foo的变量。$arg_还是一个特殊的前缀变量,也有自己的get_handler去获取,但是因为我们使用了set指令配置了,所以就不会调用get_handler去获取了。