概述
我们在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 变量的一个问题