Nginx变量的实现机制
Nginx有两种定义变量的方式,一种是在配置文件中使用set指令(由rewrite模块提供支持),另一种是在模块内定义变量。
变量相关结构体:
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; // get和set的回调参数
ngx_uint_t flags; // 变量属性
ngx_uint_t index; // 变量索引
}; typedef struct { unsigned len:; // 变量长度 unsigned valid:;
unsigned no_cacheable:;
unsigned not_found:;
unsigned escape:; u_char *data; // 变量值(只支持字符串类型)
} ngx_variable_value_t; typedef struct {
ngx_array_t servers; /* ngx_http_core_srv_conf_t */ ngx_http_phase_engine_t phase_engine; ngx_hash_t headers_in_hash; ngx_hash_t variables_hash; ngx_array_t variables; /* ngx_http_variable_t */
ngx_uint_t ncaptures; ngx_uint_t server_names_hash_max_size;
ngx_uint_t server_names_hash_bucket_size; ngx_uint_t variables_hash_max_size;
ngx_uint_t variables_hash_bucket_size; ngx_hash_keys_arrays_t *variables_keys; ngx_array_t *ports; ngx_uint_t try_files; /* unsigned try_files:1 */ ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE + ];
} ngx_http_core_main_conf_t;
在Nginx中的所有变量都是与HTTP相关的,并且基本上是同时保存在两个数据结构中:
一个是hash表(cmcf->variables_hash);另一个是数组(cmcf->variables)。
一些特殊的变量,比如arg_xxx、cookie_xxx,这些变量的名字是不确定的,而且它们是只读的,这些变量没有hash,只有索引。
解析配置阶段:
1、HTTP核心模块提供的内置变量
ngx_http_core_module模块在preconfiguration阶段的回调:ngx_http_core_preconfiguration
该函数只有一句话,调用ngx_http_variables_add_core_vars;
ngx_http_core_variables 数组 列出了所有的http core模块内置变量。
ngx_http_variables_add_core_vars 函数会把ngx_http_core_variables 数组中列出的内置变量加入到cmcf->variables_keys表中。
ngx_int_t
ngx_http_variables_add_core_vars(ngx_conf_t *cf)
{
ngx_int_t rc;
ngx_http_variable_t *cv, *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 (cv = ngx_http_core_variables; cv->name.len; cv++) {
v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t));
if (v == NULL) {
return NGX_ERROR;
} *v = *cv; 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, ,
"conflicting variable name \"%V\"", &v->name);
} return NGX_ERROR;
} return NGX_OK;
}
2、解析其它模块阶段:
除了core模块外,其它模块也会把自身支持的内部变量添加到cmcf->variables_keys内。
以proxy模块为例,在preconfiguration阶段的回调函数如下:
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函数将模块内定义的变量添加到cmcf->variables_keys。
注意,该函数仅是将变量的"name"添加进全局的hash key链表中,变量的get/set回调以及data字段,需要在这个函数返回之后进行显式的设置(由模块自己设置)。其中,data字段指向存放变量值的地方,通常是ngx_http_request_t变量r中的某个字段。
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 == ) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, ,
"invalid variable name \"$\"");
return NULL;
} cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); key = cmcf->variables_keys->keys.elts;
for (i = ; i < cmcf->variables_keys->keys.nelts; i++) {
if (name->len != key[i].key.len
|| ngx_strncasecmp(name->data, key[i].key.data, name->len) != )
{
continue;
} v = key[i].value; if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, ,
"the duplicate \"%V\" variable", name);
return NULL;
} 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 = ;
v->flags = flags;
v->index = ; rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, ); if (rc == NGX_ERROR) {
return NULL;
} if (rc == NGX_BUSY) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, ,
"conflicting variable name \"%V\"", name);
return NULL;
} return v;
}
ngx_http_add_variable函数flags参数的几个取值:
a) NGX_HTTP_VAR_CHANGEABLE,表示该变量可重复添加,后添加的会覆盖前面的同名变量;
b) NGX_HTTP_VAR_NOCACHEABLE,表示该变量可不可缓存,即每次获取变量值的时候,都需要重新调用get handler;
用户在nginx.conf使用set指令定义的变量也会被写到cmcf->variables_keys内。
并且,会将set定义的变量写入到cmcf->variables中,这是通过ngx_http_get_variable_index实现的:
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 == ) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, ,
"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, ,
sizeof(ngx_http_variable_t))
!= NGX_OK)
{
return NGX_ERROR;
} } else {
for (i = ; i < cmcf->variables.nelts; i++) {
if (name->len != v[i].name.len
|| ngx_strncasecmp(name->data, v[i].name.data, name->len) != )
{
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 = ;
v->flags = ;
v->index = cmcf->variables.nelts - ; return v->index;
}
这个函数是在cmcf->variables数组中查找指定变量,并返回该变量在数组中的索引。如果查找的变量不存在,就在数组末尾追加一个。
另外,有些变量虽然没有出现在nginx.conf中,但也可以加入到cmcf->variables中,这部分要由模块自己实现。
3、初始化变量
在解析完HTTP所有模块之后,会调用ngx_http_variables_init_vars来初始化所有的变量。
Nginx定义的变量很多(存在于cmcf->variables_keys中),但真正会用到的变量可能很少(存在于cmcf->variables中)。
ngx_http_variables_init_vars函数的意义在于:
a)遍历cmcf->variables中所有已使用的变量,初始化其get_handler和data两个成员,并指定变量索引(即在数组中的下标);
b)从cmcf->variables_keys表来初始化cmcf->variables_hash表。
这个函数其实还有合法性检查的作用,它会遍历cmcf->variables,并从cmcf->variables_keys表来查找对应的变量进行赋值。
请求处理阶段:
为每个请求分配一个数组,存放所有已使用的变量(cmcf->variables数组的个数决定)。
ngx_http_request_t *
ngx_http_create_request(ngx_connection_t *c)
{ .... 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));
if (r->variables == NULL) {
ngx_destroy_pool(r->pool);
return NULL; } ....
}
注意,这里只是分配好了存放变量的空间,并没有真的去为变量赋值;
Nginx采用的是Lazy Evaluation技术,只有真正去读它的值时,nginx才会临时执行一段代码先给它赋值,然后将结果返回。
从上面可以看出,Nginx的变量是为每个请求都保存一个副本,所以同一个变量在不同的请求中的值是可以不同的,这有点类似于请求的上下文request->ctx;
如何获取变量值
之前说过Nginx变量采用的是lazy evalution技术,如果我们在代码中不人为获取某个变量的值,这个变量其实是空的,这样做可以避免去解析不会被使用的变量。
现在来看看ngx_http_variable_t结构中get handler和set handler的函数原型:
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);
参数r是当前请求,v是变量值(ngx_variable_value_t结构),data是传入回调函数的参数,它本身位于ngx_http_variable_t结构中。
get handler的意义就是填充参数v,使其获得相应的值(保存在v->data中)。
set handler只在使用set配置指令构造脚本引擎时才会用到,例如:set $var $request_uri。配置解析完成之后,set handler就没有用了。
根据索引获取变量,前提是变量已经存在于cmcf->variables中。
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); if (cmcf->variables.nelts <= index) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, ,
"unknown variable index: %d", index);
return NULL;
} if (r->variables[index].not_found || r->variables[index].valid) {
return &r->variables[index];
} v = cmcf->variables.elts; if (v[index].get_handler(r, &r->variables[index], v[index].data)
== NGX_OK)
{
if (v[index].flags & NGX_HTTP_VAR_NOCACHEABLE) {
r->variables[index].no_cacheable = ;
} return &r->variables[index];
} r->variables[index].valid = ;
r->variables[index].not_found = ; return NULL;
}
获取变量的其它2种方式:
ngx_http_variable_value_t * ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key)
ngx_http_variable_value_t * ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_uint_t index)
其中:
ngx_http_get_flushed_variable与ngx_http_get_indexed_variable类似,不过它会处理NGX_HTTP_VAR_NOCACHEABLE这个标记。
ngx_http_get_variable则是用来获取没有索引的变量,变量存在于cmcf->variables_hash中。
Nginx变量的实现机制的更多相关文章
- Nginx 变量漫谈(七)
在 (一) 中我们提到过,Nginx 变量的值只有一种类型,那就是字符串,但是变量也有可能压根就不存在有意义的值.没有值的变量也有两种特殊的值:一种是“不合法”(invalid),另一种是“没找到”( ...
- Nginx 变量漫谈(四)
在设置了“取处理程序”的情况下,Nginx 变量也可以选择将其值容器用作缓存,这样在多次读取变量的时候,就只需要调用“取处理程序”计算一次.我们下面就来看一个这样的例子: map $args ...
- nginx源代码分析--进程间通信机制 & 同步机制
Nginx源代码分析-进程间通信机制 从nginx的进程模型能够知道.master进程和worker进程须要通信,nginx中通信的方式有套接字.共享内存.信号.对于master进程,从外部接受信号, ...
- 关于NGINX变量的一些测试结果
作为NGINX变量,不像正规语言那么正式. 但处理自定义和内部变量时,这些操作,也是少不了的. geo $dollar { default "$"; } server { list ...
- Nginx 变量漫谈(八)
与 $arg_XXX 类似,我们在 (二) 中提到过的内建变量 $cookie_XXX 变量也会在名为 XXX 的 cookie 不存在时返回特殊值“没找到”: location /test ...
- Nginx 变量漫谈(五)
前面在 (二) 中我们已经了解到变量值容器的生命期是与请求绑定的,但是我当时有意避开了“请求”的正式定义.大家应当一直默认这里的“请求”都是指客户端发起的 HTTP 请求.其实在 Nginx 世界里有 ...
- Nginx 变量漫谈(三)
也有一些内建变量是支持改写的,其中一个例子是 $args. 这个变量在读取时返回当前请求的 URL 参数串(即请求 URL 中问号后面的部分,如果有的话 ),而在赋值时可以直接修改参数串.我们来看一个 ...
- Nginx 变量漫谈(二)
关于 Nginx 变量的另一个常见误区是认为变量容器的生命期,是与 location 配置块绑定的.其实不然.我们来看一个涉及“内部跳转”的例子: server { listen ...
- Nginx 变量漫谈(一)
Nginx 的配置文件使用的就是一门微型的编程语言,许多真实世界里的 Nginx 配置文件其实就是一个一个的小程序.当然,是不是“图灵完全的”暂且不论,至少据我观察,它在设计上受 Perl 和 Bou ...
随机推荐
- 将博客搬至CSDN(放弃)
将博客搬至CSDN需要发这篇文章,但是到现在CSDN还没给我发通知,因为急着要记东西,所以不搬了,继续写我下一篇随笔.
- OAF与XML Publisher集成(转)
原文地址:OAF与XML Publisher集成 有两种方式,一种是用VO与XML Publisher集成,另一种是用PL/SQL与XML Publisher集成 用VO与XML Publisher集 ...
- 简单工厂模式 Simple Factory
简单工厂模式的作用就是定义一个用于创建对象的接口 在简单工厂模式中,一个工厂类处于对产品类实例化调用的中心位置上,它决定那一个产品类应当被实例化. 先来看看它的组成: 1) 工厂类角色 ...
- matlab绘制三维图形
原文地址:种三维曲面图. 程序如下: [x,y]=meshgrid(-8:0.5:8); z=sin(sqrt(x.^2+y.^2))./sqrt(x.^2+y.^2+eps); subplot(2, ...
- html:关于表单功能的学习
比如我在某jsp页面中写了如下表单: <form action="/MavenWeb/TestFormPost" method="get"> & ...
- 手把手教你Linux服务器集群部署.net网站 - 让MVC网站运行起来
一.Linux下面安装需要软件 我们这里需要安装的软件有: 1) Mono 3.2.8 : C#跨平台编译器,能使.Net运行与Linux下,目前.net 4.0可以完美运行在该平台下 2) ngin ...
- Git代码管理心得
一.概述: 这次按照要求进行了看似复杂,实则非常复杂并且麻烦(网上教程众多且啰嗦)的对git使用的学习,从星期六晚18:48我准备这次作业开始,直到了晚上22:44才结束电脑上的操作···(导致这篇随 ...
- Missing message for key "err1" in bundle "(default bundle)" for locale zh_CN
这个问题是: 你的使用了ApplicationResources_zh_CN.properties文件没有找到. 1.是struts-config.xml中的<message-resources ...
- javascript与服务器1
A. 通过在客户端设置Cookie,然后在服务器端读取它. 关于Cookie只说明一点, 它是存储在客户端机器上的一小块信息, 可以有客户端程序或服务器程序创建,并通过http传递.常用于跟踪用户在客 ...
- iOS边练边学--多线程NSOperation介绍,子类实现多线程的介绍(任务和队列),队列的取消、暂停(挂起)和恢复,操作依赖与线程间的通信
一.NSOperation NSOperation和NSOperationQueue实现多线程的具体步骤 先将需要执行的操作封装到一个NSOperation对象中 然后将NSOperation对象添加 ...