leevis.com icon indicating copy to clipboard operation
leevis.com copied to clipboard

nginx 的变量

Open vislee opened this issue 7 years ago • 0 comments

概述

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 函数。

添加变量代码分析

通过分析代码来看下变量是如何被添加的。

  1. 已有的变量

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数组中。

  1. 配置文件中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. 正则表达式捕获生成的变量

正则表达式捕获生成的变量,如果没有添加别名,则可以用$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

vislee avatar Feb 27 '17 03:02 vislee