最新消息:

nginx 变量的一个问题

nginx admin 2603浏览 0评论

概述

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

您必须 登录 才能发表评论!