前言:
本来的想法是写一个针对经典配置的情景分析,写到一半的时候就力不从心了,感觉很难给出一个完整全面的展示,主要是这块内容牵扯到整个系统的很多方面,比较复杂。所以就打算从一个有代表性的rewrite配置出发,讲一下在这个情景下,nginx的脚本引擎是如何工作的。
nginx的脚本解析系统,虽然不能跟专业的脚本解析器相比,但是它的设计却是非常巧妙,而且抽象性很高。先大体说一下它的大体思路。
设计思路:
我们知道nginx在解析配置时,每次根据读出的配置信息都会查找相应模块的特定处理函数,而rewrite的配置一般都是要针对具体的url来做特定的处理,如正则匹配等等,所以不能在解析的时候不能像其他配置那样得到一个相对静态的配置信息。nginx的做法是,为每个配置设置一个后续处理所需要的结构体,结构体中包含了与该配置相关的重要信息,并设置相应的处理handler,之后在处理请求的某个阶段(主要REWRITE_PHASE,这方面的介绍可以参考其他博客,这里就不重复了),会集中对这些在解析时设置的处理handler做一个集中处理。
在这个脚本引擎中,rewrite的处理或者其他使用使用脚本引擎的地方,都是通过ngx_http_script_engine_t结构体来驱动执行和保存执行结果的。我们通过下面这段代码来看一下,它的核心处理是怎么样的。
// sp是一个ngx_http_variable_value_t的数组,里面保存了从配置中分离出的一些变量
// 和一些中间结果,在当前处理中可以可以方便的拿到之前或者之后的变量(通过sp—
// 或者sp++)
e->sp = ngx_pcalloc(r->pool, rlcf->stack_size * sizeof(ngx_http_variable_value_t));
// 包含了在配置解析过程中设置的一些处理结构体,下面的rlcf->codes是一个数组,
// 注意的是,这些结构体的第一个成员就是一个处理handler,这里处理时,都会将该
// 结构体类型强转,拿到其处理handler,然后按照顺序依次执行之
e->ip = rlcf->codes->elts;
// 需要处理的请求
e->request = r;
// 初始时认为uri需要特殊处理,如做escape,或者urldecode处理。
e->quote = 1;
e->log = rlcf->log;
// 保存处理过程时可能出现的一些http response code,以便进行特定的处理
e->status = NGX_DECLINED;
// 依次对e->ip 数组中的不同结构进行处理,在处理时通过将当前结构进行强转,就
// 可以得到具体的处理handler,因为每个结构的第一个变量就是一个handler。我们看
// 的出来这些结构成员的设计都是有它的意图的。
while (*(uintptr_t *) e->ip) {
code = *(ngx_http_script_code_pt *) e->ip;
code(e);
}
说了这些抽象的理论之后,下面给出一个具体的例子来更加形象的分析一下。
情景分析:
这里我们以下面这个例子来看一下,在一个特定的rewrite情景下,nginx的脚本引擎是如何工作的:
location / {
if ($uri ~* "(.*).html$" ) {
set $file $1;
rewrite ^(.*)$ http://$http_host$file.mp4 break;
}
}
我想对于这个示例配置,大家应该都知道它的作用,主要是将一个对html请求,通过302跳转,让客户端重新取一个同名的mp4文件(是不是很无聊-_-!)。
这里通过一个图来展现在整个解析过程得到的结构及其相应的处理handler,然后我们再对一些重点的地方加以分析,对于具体的逻辑,大家可以自己看。
说明:
1. 图中蓝色的部分是if行,它的解析主要通过ngx_http_rewrite_if,set和rewirte类推。
2. 图中带有小圆圈的标记,是脚本引擎中处理handler的执行顺序,即前面的e->ip数组。
接下来我们针对这个情景下的处理流程中的一些重点进行探讨。
重点探讨:
(一) if行的解析
1. 在解析if时, nginx会把它当做一个location来对待的,并且它的location type为noname。通过ngx_http_add_location将该“location”添加到上层的locations中。这里将if看做location自然有它的合理性,因为if的配置也是需要进行url匹配的。
2. if中具体的解析通过ngx_http_rewrite_if_condition来实施。
3. 在(1)的处理时,对于$uri,ngx_http_rewrite_variable 函数会进行处理。主要的工作是将当前的这个变量(即$uri),放到全局的变量数组中,并得到其下标。还有一点就是ngx_http_script_var_code_t结构中的index,这个用来记录该下标,这样后面用到的时候就可以很快的取到该变量。
4. 为什么(2)没用设置结构体和handler呢?那是因为根据(2)的配置,就可以确定后面这个配置的处理方式了。所以这里不是每个分句都有对应的结构和handler的设置,能简单处理的,就可以捎带处理掉了,同理的还有像“break”等。
配置中凡是出现‘~’的,都是属于正则的处理。这里提醒一点是带有‘*’的,是不区分大小写的。
5. 这里先提一点相关的背景。较新的PCRE库中实现了perl和python中叫做named subpattern的东西,我们通常在引用一个pattern中的()部分时,通常使用$1,$2…等,这里的named subpattern是指在一个pattern使用类似(?<name>...)的形式,这样我们在引用这个()的内容是,就可以通过这个name来指定了,使用更加灵活。这里在(3)的处理的时候,会通过ngx_http_regex_compile函数来进行正则的编译,里面封装了一些PCRE正则库的一些API,相关的细节可以到官网http://www.pcre.org/中的document上查看。这里提一点的是,由于我们的情景中都是比较常规的正则表达式,所以与像named subpattern之类的处理就先跳过,如rc->named_captures的处理,我们这里只关注rc->captures这个值,它是正则表达式里,出现的()的个数,这个数目在对于处理后面出现的$1,$2…等非常有用。
在(3)的处理中关键的的其实就是为模式匹配提供信息,所以通过regex->test = 1来标记实际处理时应该采取的处理方式。
下一篇我们将分析set指令和rewrite执行。望关注!