概述
我们在nginx的NGX_HTTP_PREACCESS_PHASE阶段添加了几个模块,该模块抛出几个变量,用来根据用户应用做分流。例如,我们在该模块提供了$app_ups 这个变量。通过proxy_pass http://$app_ups; 来做分流。
有个需求,我们需要把python的https的流量切到另一组上游,非https的还走远来的上游服务器,我们用了map和if,当然最终这样是不可以的,我们改用了其他方法,现在不说。
当我们配置好以后在测试的时候,nginx返回500了报找不到上游服务器,就是说$app_ups这个变量为空。根据我对nginx的了解,这个是不应该的,即使没走if这个block的配置,也还是会走默认的配置,这个$app_ups变量一定会在NGX_HTTP_PREACCESS_PHASE阶段由我们添加的模块赋值的。为了再次确认,我们把这些变量添加到log输出到日志文件,也还是空。
而当把if这个block注掉后,$app_ups就有值了。又理了一遍if和map相关的代码,和我理解的是一样的,即使没走if配置的block,也会默认都我们添加的模块。没值是因为我们模块没有添加NGX_HTTP_VAR_NOCACHEABLE这个flag,当没有该flag时,第一次求值就会保存结果,以后直接使用该结果。proxy_set_header 例外,就是说你在if阶段计算了该值没结果后,在proxy_set_header ups $app_ups;会解析错误。
proxy_set_header的例外。
配置解析
在ngx_http_proxy_init_headers函数中,会把proxy_set_header配置的header的key和val添加到ngx_http_proxy_loc_conf_t的headers的lengths和values数组中,如果有变量还会把变量数组下标添加到flushes数组中。如果val里有变量,还会调用ngx_http_script_compile编译变量。编译的工作实际上还是在flushes和headers和lengths数组中添加处理函数和该函数回调的变量。
在lengths数组中添加了ngx_http_script_copy_var_len_code回调函数和对应的下标。
在values数组中添加了ngx_http_script_copy_var_code回调函数和数组的下标。
变量解析
请求到达时,中间有个过程会调用ngx_http_proxy_create_request拼接发往上游的请求的。
拼接的过程中就会通过ngx_http_script_engine_t去执行添加到lengths和values数组中的回调函数和下标。
le.ip = headers->lengths->elts;
le.request = r;
le.flushed = 1;
。。。。。。
ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
e.ip = headers->values->elts;
e.pos = b->last;
e.request = r;
e.flushed = 1;
le.ip = headers->lengths->elts;
while (*(uintptr_t *) le.ip) {
lcode = *(ngx_http_script_len_code_pt *) le.ip;
/* skip the header line name length */
// 一组header key的长度
(void) lcode(&le);
if (*(ngx_http_script_len_code_pt *) le.ip) {
// 这个循环会执行一组header的val的长度
for (len = 0; *(uintptr_t *) le.ip; len += lcode(&le)) {
lcode = *(ngx_http_script_len_code_pt *) le.ip;
}
// header的val为空时,skip是1
e.skip = (len == sizeof(CRLF) - 1) ? 1 : 0;
} else {
e.skip = 0;
}
le.ip += sizeof(uintptr_t);
while (*(uintptr_t *) e.ip) {
code = *(ngx_http_script_code_pt *) e.ip;
code((ngx_http_script_engine_t *) &e);
}
e.ip += sizeof(uintptr_t);
}
上述lcode会调用的函数:
size_t
ngx_http_script_copy_var_len_code(ngx_http_script_engine_t *e)
{
ngx_http_variable_value_t *value;
ngx_http_script_var_code_t *code;
code = (ngx_http_script_var_code_t *) e->ip;
e->ip += sizeof(ngx_http_script_var_code_t);
if (e->flushed) {
// 根据上述调用设置,会走到该分支
value = ngx_http_get_indexed_variable(e->request, code->index);
} else {
value = ngx_http_get_flushed_variable(e->request, code->index);
}
if (value && !value->not_found) {
return value->len;
}
return 0;
}
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 (cmcf->variables.nelts <= index) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
"unknown variable index: %ui", index);
return NULL;
}
// 如果已经有缓存的变量,则直接返回缓存的变量
if (r->variables[index].not_found || r->variables[index].valid) {
return &r->variables[index];
}
v = cmcf->variables.elts;
if (ngx_http_variable_depth == 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"cycle while evaluating variable \"%V\"",
&v[index].name);
return NULL;
}
ngx_http_variable_depth--;
if (v[index].get_handler(r, &r->variables[index], v[index].data)
== NGX_OK)
{
ngx_http_variable_depth++;
if (v[index].flags & NGX_HTTP_VAR_NOCACHEABLE) {
r->variables[index].no_cacheable = 1;
}
return &r->variables[index];
}
ngx_http_variable_depth++;
r->variables[index].valid = 0;
r->variables[index].not_found = 1;
return NULL;
}
ngx_http_get_flushed_variable函数是清空了原来生成的值,重新取值了。
ngx_http_get_flushed_variable函数是清空了原来生成的值,重新取值了。
总结
如果模块添加变量的时候没有设置NGX_HTTP_VAR_NOCACHEABLE这个flag,那么一个请求的生命周期内,该变量不会重复调用回调函数计算其值。也就是说你自己开发的模块添加了变量且没设置NGX_HTTP_VAR_NOCACHEABLE,而在你模块执行前使用了该变量,那么在整个请求的生命周期内该变量都没值了。
如果模块添加变量的时候设置了NGX_HTTP_VAR_NOCACHEABLE这个flag,那么一个请求的生命周期内是否会重新调用回调函数计算值,要看你取该变量调用的函数。proxy_set_header这个配置所调用的函数是不会重复取值的,access_log 会重复取值
转载请注明:爱开源 » nginx 变量的一个问题