nginx rewrite if指令剖析

来源:互联网 发布:知其雄守其雌曾国藩 编辑:程序博客网 时间:2024/05/20 11:31

0. 前言

nginxif功能确实是弱得可以,严重影响了生产效率。故此,先提出严正抗议!

1. if指令配置的实现

对于这个功能奇弱的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;

以上是基于nginx 1.2.0版本摘录出来的提纲语句。我们现在细细来讲:

i.              进入ngx_http_rewrite_if函数后,首先进行的是创建nginx http配置数据结构的过程,对应于上面的line 543line 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开始的程序段。这一部分可以这么理解,nginxif指令解释成一个单目指令,产生类似于

mov [bp], val

test [bp], 0

这样的内部指令。其中test对应着if指令,mov则是if指令括号中条件的抽象。

iv.              处理完if指令本身,nginx开始处理if指令后面的块指令。这一步对应于line 618前后的程序段。注意这里,nginx根据location配置中pclcf->name.len == 0的条件,也就是locationuri,将后续的解析指令的类型设置成为NGX_HTTP_SIF_CONFNGX_HTTP_LIF_CONF两种,也就是对应server中的iflocation中的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 691line 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

ii.              if ($var ~ ‘test’) 这个条件解析完成以后生成的内部指令序列是:

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

1.3.       块解析

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配置。

2. if指令执行的实现

if指令和其他rewrite模块指令一样,都是在处理请求的时候,在REWRITE_PHASE时被处理。处理函数是ngx_http_rewrite_handler。其核心代码如下:

 

    while (*(uintptr_t *) e->ip) {

        code = *(ngx_http_script_code_pt *) e->ip;

        code(e);

    }

由此可见,nginx的指令执行的核心其实是各个内部指令的实现本身。接下来,我们就一起来分析if涉及到的这些内部指令。

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,大家其实很容易联想到堆栈,nginxsp实现确实像个堆栈,有压栈也有出栈。

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;

因为存值的时候sp都加了1,所以判断相等时sp先减1,。接着取出比较的两个值valres。注意比较的结果存放在res中,这个过程中sp的示意图是:

before

val1

val2

sp

after

res

sp

 

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_codesp取得文件名,接着调用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;

分析这段代码可以得出if条件为真时的工作过程是使用if中的loc_conf替换请求loc_conf e->request->loc_conf = code->loc_conf;,更新请求参数,然后执行后面的内部指令e->ip += sizeof(ngx_http_script_if_code_t);。如果条件非真,则直接跳过解析if块中的指令得到的内部指令序列e->ip += code->next;

代码中明显更新的是e->request,为什么说更新的是r->request呢?因为在ngx_http_rewrite_handler()中有e->request = r;

至于nginx是如何通过location配置更新请求参数的,这个问题不在本文讨论范围内。

3. 小结

本文分析了nginx处理if条件的流程,见识了nginx内部指令的。虽然说并不是完全了解了nginx的脚本机制,但也不仅仅只是豹窥一斑的肤浅程度。

了解了这些有什么用呢?

稍微复杂点的条件写成nginx配置,怎得一个蛋疼可以形容。了解了这块的逻辑,搞个and\or\not难道是一个怎么复杂的事情?

if没有else,没有这个比没有and\or可能更麻烦。不过这个不那么容易做,但也不是不能做。

突然想到一个问题,现在这些大学的教算法教C语言,怎么就不结合这些实际的东西给学生动动手?                                                           OVER

 

0 0
原创粉丝点击