nginx的变量是怎么work的?
现在考虑一个需求,要写一个第三方模块,需要一个变量,对应着每个request,这个变量放在哪里呢?怎么获取值,怎么传递给客户端,传给后端服务器呢?
那么首先需要了解一下nginx中变量的组织结构,nginx变量可以归为两类:
类型1:nginx内部变量
nginx核心变量:ngx_http_core_variables 变量集
nginx各模块有自己的变量:比如ngx_http_browser_module 模块的 ngx_http_browsers 变量集
类型2:nginx的配置文件中,我们会用到很多变量。
比如:log_format main '$remote_addr'; 中的$remote_addr'
比如:set $name hello;中的$name
比如:limit_conn_zone $binary_remote_addr zone=ip_zone:10m; 中的$binary_remote_addr比如:set $memcached_key test; 中的$memcached_key
一:先看一下相关的变量结构:
上下文的变量结构,包括索引变量和hash变量以及hash表,这个结构在初始化完毕后就已经确定了:
typedef struct {
ngx_hash_t variables_hash; //根据variables_keys建立的hash表
ngx_array_t variables; //可以索引到的变量数组
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; //hash的变量数组
} ngx_http_core_main_conf_t;
每个请求拥有自己的索引变量,索引变量的值缓存在下面的结构中。
typedef struct {
unsigned len:28;
unsigned valid:1;//变量值是否是有效的
unsigned no_cacheable:1;.//是否能够缓存,缓存的变量直接返回data即可
unsigned not_found:1;//变量是否存在
unsigned escape:1;
u_char *data;//变量值
} ngx_variable_value_t;
上下文结构变量中的variables的元素结构,在变量初始化完毕后,是不变的。表示了一个变量的特性如flags以及获取方式如get_handler
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;
};
flags表示变量的类型,变量类型一共有四种:
NGX_HTTP_VAR_CHANGEABLE:表示此变量不能重复定义,重复定义会报错
NGX_HTTP_VAR_NOCACHEABLE:表示索引在variables中的索引变量不能缓存,每次必须重新获得值
NGX_HTTP_VAR_NOHASH:表示在variables_keys中的变量事不可以hash,初始化赋值为NULL
NGX_HTTP_VAR_INDEXED:表示在variables_keys中的变量是可以通过索引获得的
二:再看一下和变量相关的函数:
函数源码就不展示了,通过下面的解释看源码,难度不会太大。1)添加变量函数:
添加变量函数在变量进行初始化的时候使用,其中variables_keys是针对一个上下文的,而variables最后实质 是针对每个请求的
ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags);//添加变量到variables_keys
ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name);//添加变量到variables
注:通过ngx_http_get_variable_index添加到variables的变量,必须已经添加到variables_keys中去了,除非是以"http_","sent_http_","upstream_http_","cookie_","arg_"这几个字符串开头的变量。
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);
注:
点1:索引了变量可以通过 ngx_http_get_indexed_variable或ngx_http_get_flushed_variable来获取
其中ngx_http_get_flushed_variable获取索引变量中可以缓存的变量
ngx_http_get_indexed_variable 可以获取索引变量中的变量
每个请求特有的变量都是索引变量或者特殊变量
点2:ngx_http_get_variable 可以获取所有hash过的变量
这个函数首先去上下文的hash表里面找,如果找到了并且发现变量是被index了,那么就调用ngx_http_get_flushed_variable,如果此变量可以缓存,返回变量值。不可以缓存,则继续调用ngx_http_get_indexed_variable 获取值。如果变量没有被index,那么就直接调用gethandler获取值。
如果此变量没有在hash表中找到,则判断是不是特殊变量,比如http_*等,然后通过handler去获取值。
三:接下来看一下变量是如何初始化的:
nginx在ngx_http_block函数中完成了对所有变量初始化函数的调用,看一下ngx_http_block变量的三个阶段的相关代码。
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;
mi = ngx_modules[m]->ctx_index;
if (module->create_main_conf) {
ctx->main_conf[mi] = module->create_main_conf(cf);
if (ctx->main_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
if (module->create_srv_conf) {
ctx->srv_conf[mi] = module->create_srv_conf(cf);
if (ctx->srv_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
if (module->create_loc_conf) {
ctx->loc_conf[mi] = module->create_loc_conf(cf);
if (ctx->loc_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
}
pcf = *cf;
cf->ctx = ctx;
//阶段1:preconfiguration
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = ngx_modules[m]->ctx;
if (module->preconfiguration) {
if (module->preconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
/* parse inside the http{} block */
//阶段2:ngx_conf_parse
cf->module_type = NGX_HTTP_MODULE;
cf->cmd_type = NGX_HTTP_MAIN_CONF;
rv = ngx_conf_parse(cf, NULL);
if (rv != NGX_CONF_OK) {
goto failed;
}
/*
* init http{} main_conf's, merge the server{}s' srv_conf's
* and its location{}s' loc_conf's
*/
cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
cscfp = cmcf->servers.elts;
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = ngx_modules[m]->ctx;
mi = ngx_modules[m]->ctx_index;
/* init http{} main_conf's */
if (module->init_main_conf) {
rv = module->init_main_conf(cf, ctx->main_conf[mi]);
if (rv != NGX_CONF_OK) {
goto failed;
}
}
rv = ngx_http_merge_servers(cf, cmcf, module, mi);
if (rv != NGX_CONF_OK) {
goto failed;
}
}
/* create location trees */
for (s = 0; s < cmcf->servers.nelts; s++) {
clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
if (ngx_http_init_headers_in_hash(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
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;
}
}
}
//阶段3:ngx_http_variables_init_vars
if (ngx_http_variables_init_vars(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
/*
* http{}'s cf->ctx was needed while the configuration merging
* and in postconfiguration process
*/
*cf = pcf;
if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
/* optimize the lists of ports, addresses and server names */
if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
1、阶段1,对各模块进行preconfiguration:
而正是这些preconfiguration将上面类型1中的变量都注册到 variables_keys 中去了,并会根据variables_keys形成变量的hash表()
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = ngx_modules[m]->ctx;
if (module->preconfiguration) {
if (module->preconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
在调用各个模块的preconfiguration时候,会将当前模块的模块变量注册到ngx_http_core_main_conf_t结构的variables_keys变量数组中去。
有变量注册的模块有:
ngx_http_core_module:ngx_http_core_preconfiguration
ngx_http_browser_module:ngx_http_browser_add_variable
ngx_http_fastcgi_module:ngx_http_fastcgi_add_variables
ngx_http_geoip_module:ngx_http_geoip_add_variables
ngx_http_gzip_filter_module:ngx_http_gzip_add_variables
ngx_http_perl_module:ngx_http_perl_preconfiguration
ngx_http_proxy_module:ngx_http_proxy_add_variables
ngx_http_upstream_module:ngx_http_upstream_add_variables
ngx_http_userid_filter_module:ngx_http_userid_add_variables
除了ngx_http_core_module,其余模块都是通过调用ngx_http_add_variable函数,这个函数封装了底层的函数是 ngx_hash_add_key ,这个就是所有模块直接或者间接调用往variables_keys添加hash变量的。ngx_http_core_module直接调用ngx_hash_add_key添加hash变量。
那么当自己开发模块或者功能时候,需要自己定义变量的话,可以在自己的模块的preconfiguration中进行,
或者到ngx_http_core_variables中定义。在此阶段定义的变量,才能实现hash。
2、阶段2,解析配置文件ngx_conf_parse
解析配置文件的时候,会遇到类型二中描述的变量。这些变量都是随着特定cmd出现的,cmd都对应着相应的handler。一边解析conf文件,一边执行handler,
每个带有变量的handler都会调用 函数:ngx_http_get_variable_index。这个函数将会为 variables赋值。
其实这个行为就是将配置文件中的变量,全都可以通过索引快速访问。
注意:当然在阶段1中的函数里或者其他地方调用ngx_http_get_variable_index,都会将变量放到了variables中。
当然在阶段2中的函数也可以调用ngx_http_add_variable,将变量放到variables_keys中去。
也就是variables_keys是包含variables中的所有变量,除非variables变量是以"http_","sent_http_","upstream_http_","cookie_","arg_"这几个字符串开头的。
3、阶段3,调用函数ngx_http_variables_init_vars,这个函数主要是检查http{}上下文的中的ngx_http_core_main_conf_t->variables是否存在于variables_keys中,并且 将variables_keys赋值到variables。对于没有包含在variables_keys变量中的variables变量,如果是以"http_","sent_http_","upstream_http_","cookie_","arg_"这几个字符串开头的,则不会报错,因为这个或存在于header或者cookie或者请求行中,并且可以通过通用函数获得。否则会报错,因为不知道这个变量存在哪里,怎么获取值。
check完变量,将根据variables_keys这个变量数组建立变量hash表即variables_hash。hash表在此成型,不可更改了。为嘛建立hash表?这不快吗!!!具体nginx的hash表,完全需要单独一篇文章介绍,在此篇幅有限。
经历这三个阶段之后,ngx_http_core_main_conf_t变量的所有准备工作完全ok了。
四:接下来看一下每个请求是怎么拥有自己的变量的。
每个请求在初始化的时候,即调用函数ngx_http_init_request,会初始化自己的变量数组,
而这个变量数组是和请求上下文中ngx_http_core_main_conf_t->variables变量数组大小一样,并且后续也是保持一一对应的,
只是r->variables缓存了这次请求中所需要的索引变量的值,如果值无效,则可以通过对应的ngx_http_core_main_conf_t->variables的gethandler来获取值。
说白了,也就是r->variables缓存了这次请求所需的变量值,而不用实时求值了,无非就是一个效率的提高。
r_.variavbles结构:
typedef struct {
unsigned len:28;
unsigned valid:1;//变量值是否是有效的
unsigned no_cacheable:1;.//是否能够缓存,缓存的变量直接返回data即可
unsigned not_found:1;//变量是否存在
unsigned escape:1;
u_char *data;//变量值
} ngx_variable_value_t;
对应着ngx_http_core_main_conf_t->variables结构:
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;
};
下面的这个结构是不变的,确定的。
而上面的结构式跟随request改变的。
the end ————