nginx 变量(1)

2018-07-30 11:54:06

变量不仅可以用于配置文件,也可用于模块间的简单通信。

Nginx提供了两种方式找到变量:一是根据索引值直接找到数组里的相应变量。二是根据变量名字符串 hash 出的散列值,依据散列表找到相应的变量。没有第3种方式。因此,如果我们定义了一个变量,但设定为不能 hash 进入散列表,同时,使用该变量的模块又没有把它加入索引数组,那么这个变量是无法使用的。

相关结构体

存储变量值 ngx_variable_t 与 ngx_str_t 类似,表示内存的字符串空间,只是增加了几个属性。

typedef ngx_variable_value_t  ngx_http_variable_value_t;

// nginx变量值,目前仅用于http模块
typedef struct {
    unsigned    len:28;             //字符串长度,只有28位,剩下4位留给标志位

    unsigned    valid:1;            //变量值是否有效
    unsigned    no_cacheable:1;     //变量值是否允许缓存,默认允许
    unsigned    not_found:1;        //变量未找到
    unsigned    escape:1;

    u_char     *data;               //字符串的地址,同ngx_str_t::data
} ngx_variable_value_t;

nginx 在 ngx_variable_value_t 上定义了 ngx_http_variable_t 结构体,为变量值增加了一层间接层,它表示真正的 nginx 变量,对于 get / set 增加了灵活性。

typedef struct ngx_http_variable_s  ngx_http_variable_t;

struct ngx_http_variable_s {
    // 变量的名字
    ngx_str_t                     name;

    // 设置变量值函数
    ngx_http_set_variable_pt      set_handler;

    // 获取变量值函数
    ngx_http_get_variable_pt      get_handler;

    // set/get函数使用的辅助参数
    uintptr_t                     data;

    // 变量属性标志位
    ngx_uint_t                    flags;

    // 变量所在的数组序号
    ngx_uint_t                    index;
};

get / set 函数声明如下

typedef void (*ngx_http_set_variable_pt) (ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data);

typedef ngx_int_t (*ngx_http_get_variable_pt) (ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data);

结构体里的 flags 成员可以取值为下面的宏,设置变量的属性

#define NGX_HTTP_VAR_CHANGEABLE   1  //可以修改
#define NGX_HTTP_VAR_NOCACHEABLE  2  //不可以缓存
#define NGX_HTTP_VAR_INDEXED      4  //被索引
#define NGX_HTTP_VAR_NOHASH       8  //不允许hash

// 1.11.10新增的属性
#define NGX_HTTP_VAR_WEAK         16  //弱变量
#define NGX_HTTP_VAR_PREFIX       32  //有http_前缀

变量统一存储在 ngx_http_core_module 配置数据结构的 variables 成员里:

typedef struct {
    ...
    ngx_hash_t                 variables_hash;
    // 存储http里定义的所有变量
    ngx_array_t                variables;
    ngx_array_t                prefix_variables;

    //用于构建 hash,variables_hash 生成后就没用了
    ngx_hash_keys_arrays_t    *variables_keys;

} ngx_http_core_main_conf_t;

variables 是一个动态数组,它的元素是 ngx_http_variable_t,在配置解析结束时 nginx 会调用 ngx_http_variables_init_vars() 函数,把所有模块定义的可以索引的变量集中存储在这里。设置为可以 hash 的变量存储在 variables_hash 里。

variables_hash 是一个散列表,同样在 ngx_http_variables_init_vars() 函数里被初始化,可以使用函数 ngx_hash_find() 快速查找定位变量。

请求结构体里的变量

ngx_http_request_t 为变量提供了独立的存储空间用来缓存用,因此每个请求获取的变量值都是独立的。

struct ngx_http_request_s {
    ...
    ngx_http_variable_value_t  *variables;
    ...
}

在函数 ngx_http_create_request() 创建请求对象时,会依据 cmcf->variables 数组为变量值分配内存,创建缓存变量值的 variables 数组。一旦某个变量的 ngx_http_variable_value_t 值结构体被缓存,取值时就会优先使用它。

ngx_http_request_t *
ngx_http_create_request(ngx_connection_t *c){
    ...
    // 取http core的main配置
    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    // 为所有变量创建数组
    r->variables = ngx_pcalloc(r->pool, cmcf->variables.nelts
                                        * sizeof(ngx_http_variable_value_t));
    ...
}

可以看到,请求里的变量数组跟配置里的变量数组是完全对应的,但为了节约内存,r->variables 并不会主动存放变量值,只有当用户在实际访问变量时才会调用变量的 get_handler 函数,取出变量值存入数组来缓存。

ngx_http_variable_t 内存图

 

注册变量

函数 ngx_http_add_variables() 是 nginx 变量机制的核心函数,它创建一个命名的变量访问对象 ngx_http_variable_t。

ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name,
    ngx_uint_t flags);

http 模块通常使用一个静态数组存放变量定义,在配置解析前的 preconfiguration 时调用 ngx_http_add_variable(),把它们添加进 nginx 内部。

比如 ngx_http_proxy_module

static ngx_http_variable_t  ngx_http_proxy_vars[] = {

    { ngx_string("proxy_host"), NULL, 
      ngx_http_proxy_host_variable, 0, //get 函数
      NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },

    ...
}
static ngx_http_module_t  ngx_http_proxy_module_ctx = {
    ngx_http_proxy_add_variables,          /* preconfiguration */
    NULL,                                  /* postconfiguration */
    ...
};

遍历把变量存入 cmcf->variables。

static ngx_int_t
ngx_http_proxy_add_variables(ngx_conf_t *cf)
{
    ngx_http_variable_t  *var, *v;

    for (v = ngx_http_proxy_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;
}

ngx_http_add_variable() 函数会把变量加到临时变量 variables_keys 里待后续ngx_http_variables_init_vars() 来初始化使用。

// Nginx变量机制的核心函数,创建一个命名的变量访问对象
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;

    // 取core配置
    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    // 在variables_keys数组里查找
    // 不允许添加重复的变量
    // 但如果是changeable的是可以的
    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;

        // 不是changeable则报错
        if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "the duplicate \"%V\" variable", name);
            return NULL;
        }

        if (!(flags & NGX_HTTP_VAR_WEAK)) {
            v->flags &= ~NGX_HTTP_VAR_WEAK;
        }

        // 是changeable,不需要新建,直接返回对象
        return v;
    }

    // 查找完毕,没有重名

    // 新建一个变量结构体
    v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t));

    // 拷贝变量的名字,在cf的内存池里分配内存
    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;

    // 把变量的hash key加入查找数组,方便之后查找
    rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0);

    // 返回创建好的对象,注意里面的指针都是空
    return v;
}

在 ngx_http_block 里当调用完每个模块的 preconfiguration 后,会调用 ngx_http_variables_init_vars() 来把所有变量存入 cmcf->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++) {

        // 查看变量key数组
        for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) {

            av = key[n].value;

            // 通常变量都应该在keys数组里
            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;

                // 这里设置indexed标志位
                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;
    }

    // 检查是否有不要求hash的变量
    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.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;

    // 初始化hash表
    if (ngx_hash_init(&hash, cmcf->variables_keys->keys.elts,
                      cmcf->variables_keys->keys.nelts)
        != NGX_OK)
    {
        return NGX_ERROR;
    }

    // hash表初始化完毕,临时的variables_keys不再需要
    // 置为空指针,最后在tmp_pool时释放
    cmcf->variables_keys = NULL;

    return NGX_OK;
}


变量的相关方法

获取变量有两种方法,一种是索引变量,效率更高且可以被缓存,但可能会消耗多点内存。另一种是非索引的、hash过的变量,ngx_http_get_variable 方法用于此目的。

ngx_http_get_variable_index 设置变量被索引,并获得索引号,它是使用 ngx_http_get_indexed_variable、ngx_http_get_flushed_variable 方法的前置方法。调用它意味着变量会被频繁地使用,希望 Nginx 处理这个变量时效率更高,体现在:

1.变量值可以被缓存,重复读取时不用每次解析
2.定义变量的解析方法时,可以通过索引直接找到该方法进行解析,而不是通过操作散列表

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;

    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_get_indexed_variable 根据 ngx_http_get_variable_index() 得到的索引号,获取被索引过的变量的值。若变量被解析过一次后其值是会被缓存的,这样该方法再次调用后将会直接获取缓存过的值,而不是重新解析。这个方法是忽略 NGX_HTTP_VAR_NOCACHEABLE 标志位的。

// 使用index获取变量值,index即数组里的位置
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);

    // 检查index是否有效
    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是变量结构体数组
    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--;

    // 直接用index得到变量结构体,然后调用get handler
    // 获取的值缓存在请求的数组里
    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++;

    // get失败,设置为not found
    r->variables[index].valid = 0;
    r->variables[index].not_found = 1;

    return NULL;
}

ngx_http_get_flushed_variable 与 ngx_http_get_indexed_variable() 类似,区别是:如果 flags 中设置了 NGX_HTTP_VAR_NOCACHEABLE 标志位,那么本方法则会不使用已经缓存的变量值,每次取值时皆重新解析。

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) {

        // 如果允许cache那么直接返回
        if (!v->no_cacheable) {
            return v;
        }

        // 设置为无效,要求重新获取
        v->valid = 0;
        v->not_found = 0;
    }

    // 使用index重新获取
    return ngx_http_get_indexed_variable(r, index);
}

ngx_http_get_variable 根据变量名称,从被 hash 过的散列表里找到相应的变量并调用其解析方法获得值,这里不存在缓存变量值的可能。同时若变量是属于5种殊变量,也可以从本方法中获取解析出的值。

下面是5中特殊变量

 变量前缀 意义 解析方法
 arg_ 请求的uri参数 ngx_http_variable_argument
 http_ 请求头 ngx_http_variable_unknown_header_in
 sent_http_ 响应头 ngx_http_variable_unknown_header_out
 cookie_ cookie ngx_http_variable_cookie
 upstream_http_ 后端响应头 ngx_http_upstream_header_variable
ngx_http_variable_value_t *
ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key)
{
    size_t                      len;
    ngx_uint_t                  i, n;
    ngx_http_variable_t        *v;
    ngx_http_variable_value_t  *vv;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    // 用hash快速查找
    v = ngx_hash_find(&cmcf->variables_hash, key, name->data, name->len);

    if (v) {
        if (v->flags & NGX_HTTP_VAR_INDEXED) {
            return ngx_http_get_flushed_variable(r, v->index);
        }

        if (ngx_http_variable_depth == 0) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "cycle while evaluating variable \"%V\"", name);
            return NULL;
        }

        ngx_http_variable_depth--;

        vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));

        if (vv && v->get_handler(r, vv, v->data) == NGX_OK) {
            ngx_http_variable_depth++;
            return vv;
        }

        ngx_http_variable_depth++;
        return NULL;
    }

    // 没找到,可能是http、arg等前缀的变量

    vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));
    if (vv == NULL) {
        return NULL;
    }

    len = 0;

    v = cmcf->prefix_variables.elts;
    n = cmcf->prefix_variables.nelts;

    for (i = 0; i < cmcf->prefix_variables.nelts; i++) {
        if (name->len >= v[i].name.len && name->len > len
            && ngx_strncmp(name->data, v[i].name.data, v[i].name.len) == 0)
        {
            len = v[i].name.len;
            n = i;
        }
    }

    if (n != cmcf->prefix_variables.nelts) {
        if (v[n].get_handler(r, vv, (uintptr_t) name) == NGX_OK) {
            return vv;
        }

        return NULL;
    }

    // 还是没有找到,那么就值not found
    vv->not_found = 1;

    return vv;
}


变量的解析

首先回顾下解析变量的主要方法 get_handler 的方法原型

typedef ngx_int_t (*ngx_http_get_variable_pt) (ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data);

参数 r,data 都是辅助生成变量值然后存放在 v 里。v 里面的值的内存由 get_handler 负责,一般跟请求挂钩,请求结束就会释放,也就是说变量值的生命周期和请求一致。

uintptr_t data 有几种用法,我们来看一下:

1.uintptr_t data 不起作用

如果只是生成一些于请求无关的变量值,或者说 ngx_http_request_t *r 中的成员已经足够解析出变量值了,那么 data 参数不用也罢。

比如 body_bytes_sent 变量,表示响应包体长度,data 不使用则设为0。

static ngx_http_variable_t  ngx_http_core_variables[] = {
...
{ ngx_string("body_bytes_sent"), NULL, ngx_http_variable_body_bytes_sent,
      0, 0, 0 },
...
}

从 r 中就可以获取。

static ngx_int_t
ngx_http_variable_body_bytes_sent(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data)
{
    off_t    sent;
    u_char  *p;

    sent = r->connection->sent - r->header_size;

    p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);

    //指针的偏移量就是长度比如sent=13,那么v->len=2
    v->len = ngx_sprintf(p, "%O", sent) - p;
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;
    v->data = p;

    return NGX_OK;
}

2.uintptr_t data 作为指针使用

static ngx_http_variable_t  ngx_http_core_variables[] = {
...
{ ngx_string("http_"), NULL, ngx_http_variable_unknown_header_in,
      0, NGX_HTTP_VAR_PREFIX, 0 },
...
}
static ngx_int_t
ngx_http_variable_unknown_header_in(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data)
{
    return ngx_http_variable_unknown_header(v, (ngx_str_t *) data,
                                            &r->headers_in.headers.part,
                                            sizeof("http_") - 1);
}

遍历解析出请求头对应的值放入 ngx_http_variable_value_t *v。

ngx_int_t
ngx_http_variable_unknown_header(ngx_http_variable_value_t *v, ngx_str_t *var,
    ngx_list_part_t *part, size_t prefix)
{
    u_char            ch;
    ngx_uint_t        i, n;
    ngx_table_elt_t  *header;

    header = part->elts;

    for (i = 0; /* void */ ; i++) {

        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }

            part = part->next;
            header = part->elts;
            i = 0;
        }

        if (header[i].hash == 0) {
            continue;
        }

        for (n = 0; n + prefix < var->len && n < header[i].key.len; n++) {
            ch = header[i].key.data[n];

            if (ch >= 'A' && ch <= 'Z') {
                ch |= 0x20;

            } else if (ch == '-') {
                ch = '_';
            }

            if (var->data[n + prefix] != ch) {
                break;
            }
        }

        if (n + prefix == var->len && n == header[i].key.len) {
            v->len = header[i].value.len;
            v->valid = 1;
            v->no_cacheable = 0;
            v->not_found = 0;
            v->data = header[i].value.data;

            return NGX_OK;
        }
    }

    v->not_found = 1;

    return NGX_OK;
}

3.uintptr_t data 作为偏移量

有些变量值之前已经解析过了,比如http_host、http_user_agent等,它们实际上在请求头接收完时就已经解析了。这样我们就可以直接用指针来操作,避免没必要的内存复制。

static ngx_http_variable_t  ngx_http_core_variables[] = {
...
{ ngx_string("http_host"), NULL, ngx_http_variable_header,
      offsetof(ngx_http_request_t, headers_in.host), 0, 0 },

    { ngx_string("http_user_agent"), NULL, ngx_http_variable_header,
      offsetof(ngx_http_request_t, headers_in.user_agent), 0, 0 },
...
}
static ngx_int_t
ngx_http_variable_header(ngx_http_request_t *r, ngx_http_variable_value_t *v,
    uintptr_t data)
{
    ngx_table_elt_t  *h;

    h = *(ngx_table_elt_t **) ((char *) r + data);

    if (h) {
        v->len = h->value.len;
        v->valid = 1;
        v->no_cacheable = 0;
        v->not_found = 0;
        v->data = h->value.data;

    } else {
        v->not_found = 1;
    }

    return NGX_OK;
}


备注

1.测试环境centos7 64位,nginx版本为 1.14.0。
2..原文地址http://www.freecls.com/a/2712/e9


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。