对于这个功能奇弱的if指令,nginx实现得还特别复杂。下面将对其实现进行剖析。
1.1. 指令解析
if指令由ngx_http_rewrite_if函数负责解析。这个函数的主要工作是
543: ctx = ngx_pcalloc(cf->pool,
sizeof(ngx_http_conf_ctx_t)); 552: ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); 582: ngx_http_add_location(cf, &pclcf->locations, clcf); 586: ngx_http_rewrite_if_condition(cf, lcf); 590: if_code = ngx_array_push_n(lcf->codes, sizeof(ngx_http_script_if_code_t)); 618: ngx_conf_parse(cf, NULL); 632: if_code->next = (u_char *) lcf->codes->elts + lcf->codes->nelts 633: - (u_char *) if_code;
|
i. 进入ngx_http_rewrite_if函数后,首先进行的是创建nginx http配置数据结构的过程,对应于上面的line 543和line 552两个标记行引导的程序段。为什么需要这样呢?因为nginx对于块指令的指令解析回调函数仅支持两种模式。第一种方式,块内指令解析和配置存储(数据结构和存储方式)全部由用户定义,这种模式见于ngx_http_geo_block()。而第二种方式,块内指令解析和配置存储(数据结构和存储方式)则全部由nginx定义,ngx_http_rewrite_if()就属于这种方式。这种方式要求模块配置存储在ngx_http_conf_ctx_t中。
ii. 产生了nginx要求的配置存储结构以后,流程执行到line 582为终结的阶段——创建匿名location配置。之所以要创建一个匿名location,是为了实现在if匹配成功后,使用if块中的location配置替换请求的location配置。注意,这个location配置不参与nginx匹配请求uri的过程,因为有这条语句的存在clcf->noname = 1;。另外,细心的读者可能可以意识到为什么可以写在if中的配置指令无一例外都是保存在location配置中的,因为if指令在匹配上以后只会替换请求的location配置。
iii. 接着,nginx开始解析if指令后面的条件。对应line 586。如果解析成功,nginx创建一个if_code的内部指令,对应line 590开始的程序段。这一部分可以这么理解,nginx将if指令解释成一个单目指令,产生类似于
mov [bp], val
test [bp], 0
这样的内部指令。其中test对应着if指令,mov则是if指令括号中条件的抽象。
iv. 处理完if指令本身,nginx开始处理if指令后面的块指令。这一步对应于line 618前后的程序段。注意这里,nginx根据location配置中pclcf->name.len == 0的条件,也就是location的uri,将后续的解析指令的类型设置成为NGX_HTTP_SIF_CONF和NGX_HTTP_LIF_CONF两种,也就是对应server中的if和location中的if了。
v. 全部解析完成以后,nginx设置if_code->next。这个指针的用途是记录这个if块中最后一条内部指令的后续地址,一旦请求处理时if条件不满足,nginx就直接跳到这个地址执行if块后面的指令。
1.2. 条件解析
条件解析在ngx_http_rewrite_if_condition中完成。因为条件中可能含有空格,所以这个函数里面也有单独的词法分析,有点不雅,不过也没有别的办法。
659: if (value[1].len < 1 || value[1].data[0] !=
'(') { 691: if (len > 1 && p[0] == '$') { 712: if (len == 1 && p[0] == '=') { 729: if (len == 2 && p[0] == '!' && p[1] == '=') { 745: if ((len == 1 && p[0] == '~') 746: || (len == 2 && p[0] == '~' && p[1] == '*') 747: || (len == 2 && p[0] == '!' && p[1] == '~') 748: || (len == 3 && p[0] == '!' && p[1] == '~' && p[2] == '*')) 785: } else if ((len == 2 && p[0] == '-') 786: || (len == 3 && p[0] == '!' && p[1] == '-'))
|
i. 从line 659开始,到line 691结束,nginx进行了两项预处理过程。第一是剥离括号,第二是条件正文的参数下标和在该参数字符串中的字符下标。
ii. line 691是开始一个重要分支,处理形如$var op $val这样的变量条件表达式。与之相对的是line 785开始的分支,处理文件检测类的条件。
iii. line 691和line 712之间,nginx解析变量,负责这件工作的是ngx_http_rewrite_variable()函数。解析完变量后,nginx再分析变量条件表达式。分三种情况,line 712开始的变量等式,line 729开始的变量不等式和line 745开始的变量正则匹配。
举例:
i. if ($var = ‘chen’) 这个条件解析完成以后生成的内部指令序列是:
ngx_http_script_var_code
|
ngx_http_script_value_code
|
ngx_http_script_equal_code
|
ngx_http_script_if_code
|
ngx_http_script_var_code
|
ngx_http_script_regex_start_code
|
ngx_http_script_if_code
|
iii. if ( -f ‘test$i.sh’) 这个条件解析完成以后生成的内部指令序列是:
ngx_http_script_complex_value_code
|
ngx_http_script_file_code
|
ngx_http_script_if_code
|
表一:nginx条件总表
条件类型
|
内部指令集
|
备注
|
|
变量等式
|
ngx_http_script_var_code
|
值拷贝根据是否含变量分为两类
|
|
值拷贝
|
ngx_http_script_value_code
|
||
ngx_http_script_complex_value_code
|
|||
ngx_http_script_equal_code
|
|||
变量不等式
|
ngx_http_script_var_code
|
值拷贝根据是否含变量分为两类
|
|
值拷贝
|
ngx_http_script_value_code
|
||
ngx_http_script_complex_value_code
|
|||
ngx_http_script_not_equal_code
|
|||
变量正则表达式
|
ngx_http_script_var_code
|
|
|
ngx_http_script_regex_start_code
|
|||
文件检测
|
值拷贝
|
ngx_http_script_value_code
|
值拷贝根据是否含变量分为两类
|
ngx_http_script_complex_value_code
|
|||
ngx_http_script_file_code
|
if条件后面是一个块。我们前面说了,这个块中的指令类型可能是NGX_HTTP_SIF_CONF或者NGX_HTTP_LIF_CONF。我们现在来看一下,究竟有哪些指令属于这个范畴。我们不具体列出这些指令,因为nginx发展过程中,这些指令肯定会发生变量,我们这里只看他们的特点。
所有指令分为两类:
l rewrite模块指令:rewrite模块中的所有指令都可以在if产生的这两种块中被解析。解析产生的内部指令序列接在if条件的内部指令序列后面,并不断向后延伸。直到块解析完成,if_code->next指向内部指令序列的尾部。当if条件满足时,顺序指令后面的指令。当条件不满足时,通过if_code->next指针跳过块中所有指令。
l 普通指令(其他模块指令):都是将配置保存在location配置中的指令。块中的指令配置保存在刚刚建立的匿名location配置中。当if条件满足时,使用此location配置替换处理请求的location配置。
if指令和其他rewrite模块指令一样,都是在处理请求的时候,在REWRITE_PHASE时被处理。处理函数是ngx_http_rewrite_handler。其核心
while (*(uintptr_t *)
e->ip) { code = *(ngx_http_script_code_pt *) e->ip; code(e); }
|
2.1. nginx内部指令
nginx内部指令和CPU指令有点类似:op和操作数。我们刚刚看到的ngx_http_script_var_code等等就是op,其实就是个函数指针。每个op都有不同数量和类型的操作数。这些操作数和op一起放在各个不同的数据结构中,比如ngx_http_script_var_code对应的数据结构就是ngx_http_script_var_code_t。每个内部指令的整个数据结构都完整的放在nginx的内部指令序列中,就和C代码段中既有op,又有直接操作数一样。那么nginx如何识别指令呢?那就是所有数据结构的第一个属性都必须是op回调函数指针。这样一来,nginx通过ip指针指向内部指令序列的某一个地址,那个地址一定是op回调函数指针。在op回调函数,ip指针被修改,移到下一条指令的开始处,那么此地址也是下一条指令的op回调函数指针。
2.2. ngx_http_script_var_code
取得变量,*e->sp = *value; e->sp++; 这里有个问题,sp是什么?nginx内部指令处理过程中,ip指向内部指令序列,sp指向结果序列。看到sp,大家其实很容易联想到堆栈,nginx的sp实现确实像个堆栈,有压栈也有出栈。
2.3. ngx_http_script_value_code
核心代码是
1660: e->sp->len = code->text_len; 1661: e->sp->data = (u_char *) code->text_data; 1666: e->sp++;
|
这个很简单,就是字符串赋值。
2.4. ngx_http_script_complex_value_code
这个过程稍微复杂一点,但是原理还是一样的,可以看这段代码
1645: e->sp->len = e->buf.len; 1646: e->sp->data = e->buf.data; 1647: e->sp++;
|
2.5. ngx_http_script_equal_code
1426: e->sp--; 1427: val = e->sp; 1428: res = e->sp - 1; 1432: if (val->len == res->len 1433: && ngx_strncmp(val->data, res->data, res->len) == 0) 1434: { 1435: *res = ngx_http_variable_true_value; 1436: return; 1437: } 1439: *res = ngx_http_variable_null_value;
|
before:
after:
2.6. ngx_http_script_not_equal_code
它和ngx_http_script_equal_code流程完全相同,逻辑完全相反,不做赘述。
2.7. ngx_http_script_regex_start_code
ngx_http_script_regex_start_code在多种条件下使用,所以逻辑很杂。和if相关的流程如下:
931: e->sp--; 932: e->line.len = e->sp->len; 933: e->line.data = e->sp->data; 936: rc = ngx_http_regex_exec(r, code->regex, &e->line); 938: if (rc == NGX_DECLINED) { 947: if (code->test) { … 965: } 978: if (code->test) { … 992: }
|
2.8. ngx_http_script_file_code
内部指令ngx_http_script_file_code从sp取得文件名,接着调用ngx_open_cached_file()函数测试文件,最后根据测试结果将真或假存回sp。
2.9. ngx_http_script_if_code
内部指令ngx_http_script_if_code很显著的特点是一个消费者,它从sp中取出数据,判断值是否为真,但是不产生新的值。我们前面提到过if条件为真时的处理逻辑。看到
1401: if (e->sp->len &&
(e->sp->len !=1 || e->sp->data[0] != '0')) { 1402: if (code->loc_conf) { 1403: e->request->loc_conf = code->loc_conf; 1404: ngx_http_update_location_config(e->request); 1405: } 1407: e->ip += sizeof(ngx_http_script_if_code_t); 1408: return; 1409: } 1415: e->ip += code->next;
|
代码中明显更新的是e->request,为什么说更新的是r->request呢?因为在ngx_http_rewrite_handler()中有e->request = r;。
至于nginx是如何通过location配置更新请求参数的,这个问题不在本文讨论范围内。
本文分析了nginx处理if条件的流程,见识了nginx内部指令的。虽然说并不是完全了解了nginx的脚本机制,但也不仅仅只是豹窥一斑的肤浅程度。
了解了这些有什么用呢?
稍微复杂点的条件写成nginx配置,怎得一个蛋疼可以形容。了解了这块的逻辑,搞个andornot难道是一个怎么复杂的事情?
有if没有else,没有这个比没有andor可能更麻烦。不过这个不那么容易做,但也不是不能做。
声明: 此文观点不代表本站立场;转载须要保留原文链接;版权疑问请联系我们。