ngx学习——变量

1. ngx变量机制概述

变量是为了更灵活的使用配置文件

ngx配置文件支持使用变量如

if ($http_user_agent ~ MSIE) {
	rewrite ^(.*)$ /msie/$1 break;
}

变量特点:

  1. 自动赋值:对于内部变量,每个客户端请求连接里,变量都会自动被ngx赋值。对于外部变量,需要手动赋值。
  2. 惰性求值:大部分的变量只有在读取它时,ngx才会执行代码给他赋值。

2. 实现

2.1 定义

ngx将变量和变量值封装成

struct ngx_http_variable_s {
    ngx_str_t                     name;
    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 ngx_variable_value_t  ngx_http_variable_value_t;
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;

2.2 收集变量

2.2.1 收集内部变量

在解析配置文件前,各个模块要导出变量,在每个模块的preconfiguration中,将自己支持的内部变量添加到 ngx_http_core_main_conf_t 的 variables_keys

typedef struct {
	...
	ngx_hash_t    variables_hash;
	ngx_array_t    varialbes;    /* ngx_http_variable_t */
	ngx_hash_key_arrays_t  *variables_keys;
	...
} ngx_http_core_main_conf_t;

以 ngx_http_core_module为例

static ngx_int_t
ngx_http_core_preconfiguration(ngx_conf_t *cf) 
{
    return ngx_http_variables_add_core_vars(cf);
}
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 }, 
	...
};
ngx_int_t
ngx_http_variables_add_core_vars(ngx_conf_t *cf)
{
    ngx_int_t                   rc;
    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);
        
    cmcf->variables_keys = ngx_pcalloc(cf->temp_pool,
                                       sizeof(ngx_hash_keys_arrays_t));
    if (cmcf->variables_keys == NULL) {
        return NGX_ERROR;
    }   
        
    cmcf->variables_keys->pool = cf->pool;
    cmcf->variables_keys->temp_pool = cf->pool;
        
    if (ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL)
        != NGX_OK)
    {
        return NGX_ERROR;
    }
	// 添加模块支持的所有变量
    for (v = ngx_http_core_variables; v->name.len; v++) {
        rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v,
                              NGX_HASH_READONLY_KEY);

        if (rc == NGX_OK) {
            continue;
        }

        if (rc == NGX_BUSY) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "conflicting variable name \"%V\"", &v->name);
        }
    
        return NGX_ERROR;
    }   
    
    return NGX_OK;
}   

以表的形式保存变量,使用变量名求哈希得到key

ngx_int_t
ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key, void *value,
    ngx_uint_t flags)
{
	...
	// 求哈希码
    k = 0;
    for (i = 0; i < last; i++) {
        if (!(flags & NGX_HASH_READONLY_KEY)) {
            key->data[i] = ngx_tolower(key->data[i]);
        }
        k = ngx_hash(k, key->data[i]);
    }

    k %= ha->hsize;
	
    /* check conflicts in exact hash */

    name = ha->keys_hash[k].elts;

    if (name) {
        for (i = 0; i < ha->keys_hash[k].nelts; i++) {
            if (last != name[i].len) {
                continue;
            }
			// 若变量名存在,则冲突
            if (ngx_strncmp(key->data, name[i].data, last) == 0) {
                return NGX_BUSY;
            }
        }

    } else {
        if (ngx_array_init(&ha->keys_hash[k], ha->temp_pool, 4,
                           sizeof(ngx_str_t))
            != NGX_OK)
        {
            return NGX_ERROR;
        }
    }
	 //添加变量
    name = ngx_array_push(&ha->keys_hash[k]);
    if (name == NULL) {
        return NGX_ERROR;
    }

    *name = *key;

    hk = ngx_array_push(&ha->keys);
    if (hk == NULL) {
        return NGX_ERROR;
    }

    hk->key = *key;
    hk->key_hash = ngx_hash_key(key->data, last);
    hk->value = value;  //这里添加的变量是初始化完成的

    return NGX_OK;
}

2.2.2 收集外部变量

外部变量使用ngx_http_rewrite_module 的 set 指令定义

ngx_http_rewrite_set() -> ngx_http_add_variable() -> ngx_hash_add_key()

2.3 收集会被使用的变量

ngx_http_core_main_conf_t::variables_keys 收集了所有能用的变量,为了提升效率,ngx只构造配置文件中使用到的变量。

所以需要收集所有会被使用的变量

ngx_http_core_main_conf_t::variables 收集所有会被使用的变量

模块调用 ngx_http_get_variable_index,以通知Ngx自己要使用什么变量,并获得变量的索引

    // 输入变量名,返回索引
    index = ngx_http_get_variable_index(cf, &value[1]);
    if (index == NGX_ERROR) {
        return NGX_CONF_ERROR;
    }   
// 将要使用的变量不多,所以使用数组容器收集将要使用的变量
// 变量名 -> 变量的索引
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);
    // cmcf->variables中的变量此时 只占了位,并没有 构造完
    v->set_handler = NULL;
    v->get_handler = NULL;
    v->data = 0;
    v->flags = 0;
    v->index = cmcf->variables.nelts - 1; //变量的index和 变量值的index相同,而变量 和 变量值存在于不同数组,所以index是之间的桥梁

    return cmcf->variables.nelts - 1;
}

2.4 验证用户要使用的变量是否存在,并完成变量初始化

通过上面两个步骤,得到如下 集合

cmcf->variables : 所有要使用的变量,没有构造完成

cmcf->variables_keys : 所有能使用的变量,构造完成

ngx在ngx_http_variables_init_vars完成 变量合法性验证 和 剩余构造

static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
	...
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;

        if (module->postconfiguration) {
            if (module->postconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }
	// 所有模块都解析完 配置文件(http块)后
    if (ngx_http_variables_init_vars(cf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
	...
}
ngx_int_t
ngx_http_variables_init_vars(ngx_conf_t *cf) 
{
    ngx_uint_t                  i, n;
    ngx_hash_key_t             *key;
    ngx_hash_init_t             hash;
    ngx_http_variable_t        *v, *av; 
    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;
    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 (av->get_handler
                && 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; 

                goto next;
            }
        }
	// 虽然 将使用的变量 没找到,但是若名称以 xxx_ 开头,依然合法
        if (ngx_strncmp(v[i].name.data, "http_", 5) == 0) { 
            v[i].get_handler = ngx_http_variable_unknown_header_in;
            v[i].data = (uintptr_t) &v[i].name;

            continue;
        }

        if (ngx_strncmp(v[i].name.data, "sent_http_", 10) == 0) { 
            v[i].get_handler = ngx_http_variable_unknown_header_out;
            v[i].data = (uintptr_t) &v[i].name;

            continue;
        }

        if (ngx_strncmp(v[i].name.data, "upstream_http_", 14) == 0) { 
            v[i].get_handler = ngx_http_upstream_header_variable;
            v[i].data = (uintptr_t) &v[i].name;
            v[i].flags = NGX_HTTP_VAR_NOCACHEABLE;
            continue;
        }

        if (ngx_strncmp(v[i].name.data, "cookie_", 7) == 0) {
            v[i].get_handler = ngx_http_variable_cookie;
            v[i].data = (uintptr_t) &v[i].name;

            continue;
        }

        if (ngx_strncmp(v[i].name.data, "arg_", 4) == 0) {
            v[i].get_handler = ngx_http_variable_argument;
            v[i].data = (uintptr_t) &v[i].name;
            v[i].flags = NGX_HTTP_VAR_NOCACHEABLE;

            continue;
        }
	// 非法变量
        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;
}

2.4.1 自动生成的变量

有一些变量虽然没有包含在 cmcf->variables_key 中,但却是合法的。

这些变量以 http_ , sent_http_, upstream_http_ , cookie_, arg_ 开头

例如,一个类似 "http://192.168.164.2/?pageid=2" 这样的请求会自动生成变量 $arg_pageid

若用户在配置文件中使用了 变量 $arg_pageid,且请求没有参数 pageid,则变量值为空,但变量依旧合法。

2.4.2 cmcf->variables_key 的释放

ngx_http_variables_init_vars() 后,cmcf->variables_key指向NULL,因为原本指向的空间在cf->temp_pool内,在初始化基本结束后也会被释放

ngx_cycle_t *
ngx_init_cycle(ngx_cycle_t *old_cycle)
{
	...
	ngx_destroy_pool(conf.temp_pool);
}

2.5 构造完成的变量

经过上面的步骤
ngx_hash_add_key() 模块注册所有变量 -> ngx_http_get_variable_index() 注册配置文件中使用的变量 -> ngx_http_variables_init_vars() 验证并初始化 将使用的变量.

cmcf->variables 收集和所有将会使用的变量,且初始化完成

回顾属性如下

struct ngx_http_variable_s {
    ngx_str_t                     name; // 变量名称
    ngx_http_set_variable_pt      set_handler; // 写变量,只在set配置指令构造脚本引擎时采用
    ngx_http_get_variable_pt      get_handler; // 生成变量值
    uintptr_t                     data; // 标记变量值在 r 中的位置
    ngx_uint_t                    flags;
    ngx_uint_t                    index;  //在数组的index
};

2.5.1 flag字段

对于flags有:

  • NGX_HTTP_VAR_CHANGEABLE: 变量可以重复添加,主要影响ngx_http_add_variable()

set指令新增的变量都是 NGX_HTTP_VAR_CHANGEABLE,所以一下配置不会出错

	set $file t_a;
	set $file t_b;

第一个set会新增变量$file,并设置值为 t_a,第二个set会返回已有变量,并修改值。

若不是 NGX_HTTP_VAR_CHANGEABLE 变量,则会 报错: the duplicate "file" variable

  • NGX_HTTP_VAR_NOCACHEABLE: 变量不可缓存。

因为变量基本上是跟随客户端的每个连接而变的,如 $http_user_agent会随着客户端使用的浏览器不同而不同,但如果客户端在同一个连接里,这个变量肯定不会改变,所以在客户端的一个连接的整个过程中,$http_user_agent的值计算一次就行。

在比如,客户发来的连接的uri是 /aaaa.html,但是通过rewrite转换,变成了 /bbb.html&id=1,即$uri改变了,所以$uri每次使用时必须进行主动计算(调用get_handler())。

该标记影响 变量取值函数 ngx_http_get_flushed_variable()。

如果明确知道变量值不会变量,则可以使用另一个取值函数 ngx_http_get_indexed_variable(),直接取值不考虑是否可缓存标记。

2.5.2 data字段

data指向存放变量值的地址,具体是 ngx_http_request_t 变量 r 中的某个字段。

因为ngx内部变量大部分直接或间接的来自变量r的某些字段,如内部变量$args的data字段指向变量r中的args字段,这是直接情况。

间接情况:如$remote_port,在ngx_http_request_t内没有直接的字段,但是肯定同样来自r变量里,怎么获取就看get_handler()的实现,此时data字段没有用处,值为0

2.5.3 get_handler字段

实现间接获得变量值
以ngx_http_core_module为例

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 },
...};

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;
}

2.6 变量和变量值

2.6.1 变量和变量值的内存结构

ngx将变量和变量值分开,为什么要分开呢?

为了节省内存,因为变量值和客户端请求数量相关,若有3个客户端请求正在处理,则变量$args会有三份,若变量和变量值在同一个结构体,则变量也存在三份,导致内存浪费。

所以变量只会保存一次,变量值可能有多个
ngx学习——变量

2.6.2 变量和变量值如何关联

变量存在于 cmcf->variables数组

变量值需要分配空间,ngx在处理每一个客户端请求时,初始化函数ngx_http_init_request()内创建了这个空间

static void 
ngx_http_init_request(ngx_event_t *rev)
{
	...
	r->variables = ngx_pcalloc(r->pool, cmcf->variable.nelts * 
                      sizeof(ngx_http_variable_value_t));
	...
}

这个变量和cmcf->variables是一一对应的,通过下表关联

先通过ngx_http_get_variable_index()获得变量的index,然后用这个index去变量值数组里取对应空间

上一篇:ngx——naxsi——示例


下一篇:ngx学习——配置解析