set指令
set语法格式是 set $name value或者set $name$value或者 set $name string1${value}string2
set对应的处理函数是 ngx_http_rewrite_set
首先查看set $name value这种类型的执行过程。
1,检查name是不是以$开头
代码如下:
if (value[1].data[0] != '$') {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid variable name \"%V\"", &value[1]);
return NGX_CONF_ERROR;
}
2,调用ngx_http_add_variable把name加入到core_module 的main_conf里面的variables_keys,需要注意的是set的变量对应的类型(flag)都是NGX_HTTP_VAR_CHANGEABLE,返回ngx_http_variable_t类型的变量v
ngx_http_add_variable流程为 :
首先检查variables_keys里面有没有已经包含这个变量;
如果有,查看变量的标志是不是NGX_HTTP_VAR_CHANGEABLE(可被修改)),如果没有则表明该变量已经有过定义,并且不允 许修改,则返回NULL,并打印错误log,“the duplicate ****variable”
如果没有,申请ngx_http_variable_t类型的空间;
赋值;把变量的名字(name)拷贝到v->name中,把set_handler,get_handler都设置为NULL,data,index都设置为0,flags设置
为NGX_HTTP_VAR_CHANGEABLE
nginx的ngx_hash_keys_arrays_t结构:hsize代表会有多少个桶,keys_hash是一个数组,每个桶都为一个数组,可以无限存放;还有一个keys变量,存放的是东西第二点后面有详细标注,什么时候调用还没有看到?
调用ngx_hash_add_key把申请的变量加入到cmcf->variables_keys中,使用变量名根据ngx_hash函数计算出一个值,然后对variable_keys的hsize取余,计算出hash的桶所在位置,接下来对cmcf->variables_keys->keys_hash进行验证,查看是否已经占用,如果没有占用,则把变量名存放到keys_hash中对应的桶中;接下来对cmcf->variables_keys->keys进行赋值,存储的类型为ngx_hash_key_t,其变量key对应的是变量名,其变量key_hash存储的是变量名经过ngx_hash_key计算出来的值,其变量value存储的是上文分配的ngx_http_variable_t类型空间
3,调用ngx_http_get_variable_index函数,获取声明的变量name对应的index
该函数的工作流程为,首先获取到 cmcf->variables.elts,然后进行查找,如果有则返回存储的下标,如果没有,则把该变量存到cmcf->variables里面,其类型为ngx_http_variable_t,新申请的变量的name字段即为设置的变量名字,set_handler和get_handler都赋值为NULL,data和flags设置为0,index从0开始,新加入的依次排列,排到多少对应的index就是多少。然后插入时的序号。
4,如果不是http_ sent_http_ 等六类泛型变量,则把设置的变量的get_handler设置为ngx_http_rewrite_var,同时把data设置为index;变量v为cmcf->variables_keys中申请的变量,不是cmcf->variables中申请的v
5,ngx_http_rewrite_value,函数的调用实参为ngx_http_rewrite_value(cf, lcf, &value[2])
首先查看调用set时设置的value,有无变量(set $name $value;或者set $name value);
无变量的情况下,也就是set $name value这种格式;
调用ngx_http_script_start_code函数返回ngx_http_script_value_code_t类型的指针val;
ngx_http_script_start_code函数主要目的为rewrite_module的loc_conf的codes变量分配空间,其中codes为ngx_array_t类型,分配的单元为1个字节。返回需要使用的空间的首地址。
接下来判断set指令设置变量时变量的值是否是纯数字,如果是数字则把表示的十进制数字计算出来,赋值到val的value字段。需要注意的是在判断是否是纯数字的时候直接使用的是返回值NGX_ERROR,对于ngx_atoi如果值的长度为0也会返回NGX_ERROR,在这个点不需要考虑这种情况的原因是:set指令的格式是携带两个参数,在该函数之前已经对函数个数进行过检测,所以在此已经可以确保value是有值的,也就是NGX_ERROR只会代表一种情况:不是数字,是字符串。接下来对val->code赋值为ngx_http_script_value_code,val->text_len赋值为set指令设置的值的长度,val->text_data赋值为set指令设置的值,如果set $name value,那text_data就为value。
6,对vcode赋值,类型为ngx_http_script_var_code_t,通过ngx_http_script_start_code函数申请空间,注意类型不一样,当前为ngx_http_script_var_code_t,之前为ngx_http_script_value_code_t。接下来对于vcode的code赋值为ngx_http_script_set_var_code,vcode的index赋值为对该变量设置的索引
调用
在rewrite_phase阶段,会执行rewrite_module的处理函数ngx_http_rewrite_handler,当rlcf->codes为空时,即没有设置变量时,直接执行下一个模块,当rlcf->codes不为空时,则会调用脚本引擎,具体执行流程为:
首先分配ngx_http_script_engine_t类型的变量e,对e->sp申请空间,e->ip指向rlcf->codes->elts,e->request指向当前r,e->quote设置为1,e->log设置为rlcf->log,e->status设置为NGX_DECLINED,接下来就是调用存储在rlcf->codes里面的函数指针,以及存储的值。针对set $name value这种格式的,首先会调用ngx_http_script_value_code,把值会保存在e->sp->data里面;接下来会调用ngx_http_script_set_var_code函数,该函数会把上一个步骤保存的值放在r->variables[index]里面,接下来取的时候就可以根据变量对应的索引来获取该值。
有个问题是:看到e->sp分配的空间是10倍的ngx_http_variable_value_t空间,这个10在哪里有限制,还没有看到。
再查看这种类型的处理过程set $name string1${value}string2
1-4步都和简单情况处理一致
5,ngx_http_rewrite_value 该函数传递的实参为ngx_http_rewrite_value(cf, lcf, &value[2])
首先调用ngx_http_script_variables_count获取value[2]里面包含的变量个数,通过遍历全部字符,查找$来判断
当变量的个数大于0时,通过ngx_http_script_start_code函数为lcf->codes分配空间,返回sizeof(ngx_http_script_complex_value_code_t)个字节数存储空间的首地址,赋值给complex,
然后把complex->code设置为ngx_http_script_complex_value_code,
complex->lengths赋值为NULL。
注意:lcf->codes和complex的地址不一样,lcf->codes指向的是ngx_array_t类型的首地址,comple指向的是ngx_array_t里面的elts指向的空间,也就是complex和lcf->codes的取*结果是一致的。
complex->code什么时候使用?
ngx_http_script_complex_value_code函数执行过程如下:
首先把e->ip设置为下一个执行函数的起始地址,(先暂时往下执行)
接下来对ngx_http_script_compile_t类型变量sc赋值,
sc.cf赋值为cf,
sc.source设置为set指令的值,也就是$value,
sc.lengths设置为complex的lengths地址,(根据名字,也就是该字段会得出长度)
sc.values设置为lcf->codes地址(根据名字,也就是该字段会产生值)
sc.variables设置为n,就是有多少个变量,
sc.complete_lengths赋值为1;
接下来调用ngx_http_script_compile函数,传递的实参为≻ngx_http_script_compile(&sc) 函数调用过程为:
首先调用ngx_http_script_init_arrays(sc),该函数主要是对sc->flushes,sc->lengths(后面会用到),sc->values,sc->variables在为NULL时候进行初始化
接下来遍历sc->source,在不包含正则表达式的时候,接下来就是对于字符串进行分割,
有三种类型交互的可能:纯字符串,变量($开头),正则表达式$1,2等,$1这种是调用pcre库获取对应的捕获到串,具体可以
man pcreapi。
如果是先是字符串,则调用ngx_http_script_add_copy_code函数,
该函数的调用为ngx_http_script_add_copy_code(sc, &name, (i == sc->source->len),
执行过程如下:首先分配sizeof(ngx_http_script_copy_code_t)的空间,分配的空间是在sc->length(complex)上
对其内存赋值,code变量赋值为ngx_http_script_copy_len_code,len变量赋值为纯字符串的长度,
接下来在sc.values(lcf->codes)分配size个字节,size就是上一步sizeof(ngx_http_script_copy_code_t)+len通过uintptr_t进行align之后的数值,接下来对其成员code赋值为ngx_http_script_copy_code,成员len赋值为纯字符串的长度,接下来把获取到的纯字符串拷贝到code之后的sizeof(ngx_http_script_copy_code_t)为起始地址的空间内,
接下来是个变量
会调用ngx_http_script_add_var_code函数,调用函数ngx_http_script_add_var_code(sc, &name) (示例 set $name string$value,name就为value,其中value只能为set定义的变量和内置变量?)
ngx_http_script_add_var_code函数执行过程为:
首先获取name的index,如果sc->flushes存在,则把index存到sc->flush中,接下来在sc->lengths上分配sizeof(ngx_http_script_var_code_t)个字节的空间,对其code赋值,设置为ngx_http_script_copy_var_len_code,index设置为该变量的index,接下来在sc->values上面分配sizeof(ngx_http_script_var_code_t)个字节的空间,调用时第三个参数不为空,接下来会对其code赋值为ngx_http_script_copy_var_code,index赋值为该变量的index
在lcf->codes的最后加入的调用函数是ngx_http_script_set_var_code,code->index是通过set的变量的index。
获取变量值提供了两种方式
1,ngx_http_script_run函数,这种一般用在自定义的一些情况,比如设置了子请求,然后子请求里面有变量
该函数执行顺序为:
首先执行sc->length里面的函数,获取变量展开之后的长度,接着执行sc->values里面的函数,进行赋值操作。
2,ngx_http_rewrite_handler,这种是nginx框架调用
遍历lcf->codes里面的函数,把计算出来的值放在r->variable[index]里面。