一、变量概述
在Nginx中同一个请求需要在模块之间数据的传递或者说在配置文件里面使用模块动态的数据一般来说都是使用变量,比如在HTTP模块中导出了host/remote_addr等变量,这样我们就可以在配置文件中以及在其他的模块使用这个变量。在Nginx中,有两种定义变量的方式,一种是在配置文件中,使用set指令,一种就是上面我们提到的在模块中定义变量,然后导出。 在Nginx中所有的变量都是与HTTP相关的(也就是说赋值都是在请求阶段),每一个变量都是一个ngx_http_variable_s结构体的实例,具体如下:
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;
};
其中,name表示对应的变量名字,set/get_handler表示对应的设置以及读取回调,而data是传递给回调的参数,flags表示变量的属性,index提供了一个索引(数组的脚标),从而可以迅速定位到对应的变量,set/get_handler只有在真正读取设置变量的时候才会被调用。
二、变量创建
在Nginx中,创建变量有两种方式,分别是在配置文件中使用set指令,和在模块中调用对应的接口,在配置文件中创建变量比较简单(例如:set $a "hello world"),因此,我们主要来看如何在模块中创建自己的变量。在Nginx中提供了下面的接口,可以供模块调用来创建变量:
ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags);
这个函数所做的工作就是将变量“name”添加进全局的hash key表中,然后初始化一些域。
需要注意的是:对应变量的get/set回调,需要当这个函数返回之后,显式地设置,比如在split_clients模块中的例子:
var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
...
//设置回调
var->get_handler = ngx_http_split_clients_variable;
var->data = (uintptr_t) ctx;
而对应的回调函数原型如下:
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);
其中,第一个参数是当前请求,第二个是需要设置或者获取的变量值,第三个是初始化时的回调指针,这里我们着重来看一下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;
这里重点是data域,在get_handle中设置变量值的时候,只需将对应的值放入到data中就可以了,data需要在get_handle中分配内存,举例说明如下:
v->len = f->script_name.len + flcf->index.len;
v->data = ngx_pnalloc(r->pool, v->len);
...
p = ngx_copy(v->data, f->script_name.data, f->script_name.len);
ngx_memcpy(p, flcf->index.data, flcf->index.len);
三、变量使用
Nginx的内部变量指的就是Nginx的官方模块中所导出的变量,在Nginx中,大部分常用的变量都是CORE HTTP模块导出的。而在Nginx中,不仅可以在模块代码中使用变量,而且还可以在配置文件中使用。
假设我们需要在配置文件中使用http模块的host变量,那么只需要这样在变量名前加一个$符号就可以了($host),但是如果需要在模块中使用host变量,那么就比较麻烦,需要先取索引再取变量,即:
1、取索引
根据变量名,通过如下接口来取得对应变量名的索引值:
ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name);
2、取变量
根据索引,通过如下接口来取得变量:
ngx_http_variable_value_t *ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index);
ngx_http_variable_value_t *ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_uint_t index);
ngx_http_variable_value_t *ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key);
前两者用来取得有索引的变量(二者区别是前一个会处理NGX_HTTP_VAR_NOCACHEABLE标记,即能cache变量值,后一个真好相反),而最后一个可以取得无索引的变量。
3、举例说明
下面以http_log模块为例说明之,假设在log_format中配置了对应的变量,那么它首先会调用ngx_http_get_variable_index来保存索引:
static ngx_int_t ngx_http_log_variable_compile(ngx_conf_t *cf, ngx_http_log_op_t *op, ngx_str_t *value)
{
ngx_int_t index;
//得到变量的索引
index = ngx_http_get_variable_index(cf, value);
if (index == NGX_ERROR) {
return NGX_ERROR;
}
op->len = 0;
op->getlen = ngx_http_log_variable_getlen;
op->run = ngx_http_log_variable;
//保存索引值
op->data = index;
return NGX_OK;
}
然后http_log模块会使用ngx_http_get_indexed_variable来得到对应的变量值。需要注意的是:使用这个接口的时候,不仅要判断返回值是否为空,还需要判断value->not_found,因为只有第一次调用才会返回空,后续返回就不是空,因此需要判断value->not_found:
static u_char *ngx_http_log_variable(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op)
{
ngx_http_variable_value_t *value;
//获取变量值
value = ngx_http_get_indexed_variable(r, op->data);
if (value == NULL || value->not_found) {
*buf = '-';
return buf + 1;
}
if (value->escape == 0) {
return ngx_cpymem(buf, value->data, value->len);
} else {
return (u_char *) ngx_http_log_escape(buf, value->data, value->len);
}
}
四、雕虫小技
1、定义
变量在使用前,必须已经创建了,否则会Nginx服务器会拒绝加载配置:
[emerg] unknown "xxx" variable
2、类型
Nginx的配置文件中可以设置自定义变量(set $a hello;),也可以使用内置变量(Nginx核心和各个Nginx模块提供的“预定义变量”),但是无论哪种变量都只有字符串一种类型,即都只能存放字符串。
3、作用域
Nginx变量一旦创建,其变量名的可见范围就是整个Nginx配置,甚至可以跨越不同虚拟主机的server配置块,但每个请求都有所有变量的独立副本,或者说都有各变量用来存放值的容器的独立副本,彼此互不干扰,Nginx变量的生命期是不可能跨越请求边界的。
------------------------------------------------
以上详情可参看
这里。
4、生命期
Nginx变量容器的生命期,并不严格与location配置块绑定,比如“内部跳转”,但“外部跳转”是会与location配置块绑定的。
------------------------------------------------
以上详情可参看
这里。
5、存储器
在读取变量时执行的这段特殊代码,在 Nginx 中被称为“取处理程序”(get handler);而改写变量时执行的这段特殊代码,则被称为“存处理程序”(set handler)。不同的Nginx模块一般会为它们的变量准备不同的“存取处理程序”,从而让这些变量的行为充满魔法。
------------------------------------------------
以上详情可参看
这里。
6、缓存机制
在设置了“取处理程序”的情况下,Nginx 变量也可以选择将其值容器用作缓存,这样在多次读取变量的时候,就只需要调用“取处理程序”计算一次。
------------------------------------------------
以上详情可参看
这里。
7、父子请求
变量的生命期是与当前请求相关联的。每个请求都有所有变量值容器的独立副本,只不过当前请求既可以是“主请求”,也可以是“子请求”。即便是父子请求之间,同名变量一般也不会相互干扰,但也有特例,不过最好禁用父子请求间的变量共享。
------------------------------------------------
以上详情可参看
这里。
8、无值变量
Nginx变量的值只有一种类型,那就是字符串,但是变量也有可能压根就不存在有意义的值。没有值的变量也有两种特殊的值:一种是“不合法”(invalid),另一种是“没找到”(not found)。如何区分空值与这两种特殊情况,可以利用第三方模块,比如:用来解决。
------------------------------------------------
以上详情可参看
这里。
9、变量插值
在配置指令的参数中使用未定义的变量不会触发“变量插值”(否则Nginx会在启动时抱怨变量未创建)。这是因为content_by_lua配置指令并不支持参数的“变量插值”功能。之所以这样是因为配置指令的参数是否允许“变量插值”,其实取决于该指令的实现模块。
10、其它类型
任何事情都不绝对,Nginx变量只有字符串这一种数据类型,但这并不能阻止像这样的第三方模块让Nginx变量也能存放数组类型的值。
------------------------------------------------
以上详情可参看
这里。