本文将结合实际的源码来探讨nginx的脚本实现原理,并会在最后对此进行总结。本次只展示复杂变量,对于其if等指令后续文章再来探讨。
nginx的脚本支持使其具备了强大的灵活性,我们可以使用简单的脚本指令配置,进行灵活的功能定制。欲了解此功能,必先了解其变量的实现原理.(nginx变量),先看3个配置示例:
示例1:
location / {
if ($host = “127.0.0.1”) {
return 200 'you request is from local';
}
}
示例2:
map $host $proxy{
127.0.0.1 127.0.0.1:8080;
default 127.0.0.1:80;
}
...
location / {
proxy_pass $proxy;
}
示例3:
location / {
proxy_pass '127.0.0.1:$my_port'
}
我们可以根据一些参数的值,来走指定的业务。
下面我们来看http_proxy_module源码为例来解析。
下面先说说nginx的脚本(复杂变量)实现的基本原理,以http为例。
其主要源码在http/ngx_http_script.c中
基本原理:
将指令翻译成一个个执行单元,然后依次执行每个单元。
是不是很简单就一句话,但是深究起来还是很复杂的,但是首先要记住这个基本的原理。每个执行单元就好比我们的一条cpu执行指令一样。执行单元其本质就是一个函数,就是依次调用每个函数而已。
我们以proxy_pass ‘127.0.0.1:$my_port’ 这个配置为例
前面是常量字符串,加一个变量$my_port,那么我们最终需要做的就是将常量+变量计算出来的值相加后,得到代理的完整地址。
然后我们结合源码来解释。
先看几个结构体
ngx_http_script_engine_t,看字面意思是脚本引擎,我们可以将其看做是cpu
typedef struct {
ngx_http_script_code_pt code;//执行函数(函数指针)
uintptr_t len; //值长度,变量值的长度
} ngx_http_script_copy_code_t;
字面意思是脚本拷贝指令,其就是一个执行单元,结构体以code_t结尾的基本都是执行单元。
此结构体计算的是常量,len等于常量的字符串长度
typedef struct {
ngx_http_script_code_pt code;
uintptr_t index; //变量索引值
} ngx_http_script_var_code_t;
ngx_http_script_engine_t会依次执行以code_t结尾的单元中的函数 code,nginx会每个执行翻译(编译)成一个对于的code_t结尾的结构体,最终使用ngx_http_script_engine_t来执行其中的每个结构体的code函数
此结构体计算的是变量,index是该变量的在全局数组的下标,code函数会去取变量的值(如果变量是不可缓存的,直接调用变量的get函数获取到变量的值和长度)
在函数 ngx_http_script_run中可以看到下面的代码
while (*(uintptr_t *) e.ip) {
code = *(ngx_http_script_code_pt *) e.ip;
code((ngx_http_script_engine_t *) &e);//依次执行每个单元,每个code函数中,会做e.ip的偏移操作,使其偏移到下个code的地址,即e.ip会保存下个code的起始地址
}
观察所有的code_t结尾的结构体的第一个成员都是ngx_http_script_code_pt code;因此上面的代码才可以这样实现,不管code_t是怎样的,但是首地址都是执行函数地址。
下面具体参考http_proxy_module模块来说,ng是怎么翻译(编译)复杂变量的,以及是怎么执行这些单元的
一、翻译(编译)基本流程(在ngx_http_proxy_pass中实现):
1.获取配置中的变量个数ngx_http_script_variables_count(个数>0走下面的步骤),即$的个数
2.执行ngx_http_script_compile对脚本进行编译
2.1 调用ngx_http_script_init_arrays主要是初始化两个数组(ngx_array_t),lengths和values,
lengths中依次存的是计算变量长度的执行单元(code)
values中依次存在的是计算变量值的执行单元(code)
因为nginx需要先计算整个复杂变量的总长度,然后才能分配足够的内存空间来依次存放每个变量值,得到最终的值。(在计算长度的时候,就可能调用变量的get函数,该函数会计算出值,如果值是可缓存的,后续计算值的时候,就可以不再调用get方法了,以此可以提高效率)
2.2 遍历出所有的变量,为每个变量构造一个code_t的结构体存储,其中常量也是一个变量
对于'you local host is $host and port is $port',这样的,分割出来就是4个变量,
‘’you local host is ‘是第一个变量,此变量为常量(含is后面的空格)
$host为第二个,
' and port is '是第三个变量,此变量也为常量
$port为第四个变量
依次为此4个变量生产执行单元,以code_t结尾的结构体.
其中常量使用的是ngx_http_script_copy_code_t,其中的len就是常量字符串的长度,调用ngx_http_script_add_copy_code进行添加到lengths和values中去
变量使用的是ngx_http_script_var_code_t,其中的index就是该变量在全局数组中的下标,调用ngx_http_script_add_var_code进行添加到lengths和values中去
2.3 最后调用 ngx_http_script_done 来结束脚本编译。其目的就是在lengths和values的尾部添加2个空的code,用以标记脚本的结束。
二、脚本的执行
调用 ngx_http_script_run来执行之前生产好的脚本数组,其实现比较简单
u_char * ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value,
void *code_lengths, size_t len, void *code_values)
{
ngx_uint_t i;
ngx_http_script_code_pt code;
ngx_http_script_len_code_pt lcode;
ngx_http_script_engine_t e;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
//变量重置,如果变量不可缓存,则清除其valid和not_found标志位,使其每次获取都需要重新计算,即重新调用其get_handler方法
for (i = 0; i < cmcf->variables.nelts; i++) {
if (r->variables[i].no_cacheable) {
r->variables[i].valid = 0;
r->variables[i].not_found = 0;
}
}
ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
e.ip = code_lengths;//code_lengths为上面所说的存放计算变量长度code的数组 lengths
e.request = r;
e.flushed = 1;
while (*(uintptr_t *) e.ip) {
lcode = *(ngx_http_script_len_code_pt *) e.ip;
len += lcode(&e);//执行每个计算长度的code,返回的长度累加,得到整个复杂变量的总长
}
value->len = len;
value->data = ngx_pnalloc(r->pool, len);//分配可以存储整个复杂变量的内存空间
if (value->data == NULL) {
return NULL;
}
e.ip = code_values;//code_values为上面所说的存放计算变量值的code数组values
e.pos = value->data;//指向变量值的内存空间,在执行单元函数内,会对其进行偏移
while (*(uintptr_t *) e.ip) {
code = *(ngx_http_script_code_pt *) e.ip;
code((ngx_http_script_engine_t *) &e);//依次执行每个code,得到的值填充到e.pos中
//然后对e.pos进行偏移操作
}
return e.pos;
}
我们来具体看一个变量长度的计算函数和变量值计算函数
size_t ngx_http_script_copy_var_len_code(ngx_http_script_engine_t *e)
{
ngx_http_variable_value_t *value;
ngx_http_script_var_code_t *code;
code = (ngx_http_script_var_code_t *) e->ip;//获取到当前的code_t
e->ip += sizeof(ngx_http_script_var_code_t);//e->ip指向下个code_t
if (e->flushed) {
value = ngx_http_get_indexed_variable(e->request, code->index);//计算变量的具体值,根据
//index找到变量,然后调用 变量的get_handler方法
} else {
value = ngx_http_get_flushed_variable(e->request, code->index);
}
if (value && !value->not_found) {
return value->len; //变量计算ok,返回变量长度
}
return 0;
}
变量值计算函数
void ngx_http_script_copy_var_code(ngx_http_script_engine_t *e)
{
u_char *p;
ngx_http_variable_value_t *value;
ngx_http_script_var_code_t *code;
code = (ngx_http_script_var_code_t *) e->ip;//获取当前code_t
e->ip += sizeof(ngx_http_script_var_code_t);//指向下个code_t
if (!e->skip) {
if (e->flushed) {
value = ngx_http_get_indexed_variable(e->request, code->index);//计算变量值
} else {
value = ngx_http_get_flushed_variable(e->request, code->index);
}
if (value && !value->not_found) {
p = e->pos;
e->pos = ngx_copy(p, value->data, value->len);//变量值填充到e->pos,并偏移e->pos
ngx_log_debug2(NGX_LOG_DEBUG_HTTP,
e->request->connection->log, 0,
"http script var: \"%*s\"", e->pos - p, p);
}
}
}
总结:
编译流程:
1.获取变量数量
2.根据变量数量和原始配置初始化lengths和values数组,数组中存储的都是code_t的结构体,这两个数组存储在http_proxy_module模块对应的location位置,其中也是根据lengths是否为空来做判断是否需要调用ngx_http_script_run来执行
3.为每个变量(常量和变量)构造code_t,前者是copy_code_t 后者是var_code_t,分别依次存入lengths和values数组中,最终调用ngx_http_script_done结束脚本的构造
执行流程:
1.依次执行lengths中的code_t,累加得到整个长度
2.分配足够的内存空间
3.依次执行values中的code_t,计算的值依次填入到分配好的空间内
标签:code,http,变量,script,value,nginx,详解,ngx From: https://blog.csdn.net/wb1986218/article/details/139589546