1. ngx变量机制概述
变量是为了更灵活的使用配置文件
ngx配置文件支持使用变量如
if ($http_user_agent ~ MSIE) {
rewrite ^(.*)$ /msie/$1 break;
}
变量特点:
- 自动赋值:对于内部变量,每个客户端请求连接里,变量都会自动被ngx赋值。对于外部变量,需要手动赋值。
- 惰性求值:大部分的变量只有在读取它时,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会有三份,若变量和变量值在同一个结构体,则变量也存在三份,导致内存浪费。
所以变量只会保存一次,变量值可能有多个
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去变量值数组里取对应空间