继续上一篇的分析,下面我们将主要谈论set和rewrite两行的解析处理。为了方便分析,这里把示例配置和调用图列出来
location / {
if ($uri ~* "(.*).html$" ) {
set $file $1;
rewrite ^(.*)$ http://$http_host$file.mp4 break;
}
}
(一) set行的解析
1. 我们首先看这里这句:
ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE);
为什么这个函数的第三个参数flag,被设置成NGX_HTTP_VAR_CHANGEABLE,那是因为set之后的第一个的变量可以在一个if配置里面出现多次,每次可以改变它的值,当然还有nochangeable的,后面碰到再说。紧接着,把这个变量放到全局变量数组中,同时记录它的所在位置(即下标index),即函数ngx_http_get_variable_index。
2. 通过图中所示,先处理的是set后面的第二个变量,那第一个怎么办呢?先处理它使用原因的,一会就可以看到了。这个变量通过ngx_http_rewrite_value函数来处理的,在处理时带有$的变量,nginx里面称作“complex value”,于是分配ngx_http_script_complex_value_code_t结构同时使用ngx_http_script_complex_value_code来处理,接下来有几行代码需要注意下:
// lengths和values数组会在ngx_http_script_compile的执行过程中被填充 sc.lengths = &complex->lengths; sc.values = &lcf->codes; // complete_lengths置1,是为了给lengths数组结尾(以null指针填充),因为在运 // 行这个数组中的成员时,碰到NULL时,执行就结束了。 sc.complete_lengths = 1;
3. ngx_http_script_compile函数算是这里的一个核心,主要是分离当前“complex value”中的一些不同类型的变量,具体逻辑不难看懂,我们只把一些需要注意的地方强调下就行了。
在开始的时候有个函数叫ngx_http_script_init_arrays,这个函数对flushes,lengths和values三个数组的初始容量给出了一个估计值,当然nginx的数组结构在初始空间不够的时候会自动扩充的,这个就不用太追究了。
在函数ngx_http_script_add_capture_code中,有个这样的赋值动作code->n = 2 * n,为什么是2*n呢?这个跟PCRE保存匹配结果集的结构有关,后面我们会讲到。
4. 在处理set 中的第一个参数的时候,它的handler被置为了ngx_http_script_set_var_code,通过这个函数,我们也就知道了为什么要先处理后一个参数:
// e->ip就是之前在解析时设置的各种结构体 code = (ngx_http_script_var_code_t *) e->ip; e->ip += sizeof(ngx_http_script_var_code_t); r = e->request; // e->sp是通过解析得到的变量处理结果的一个数组,变量的放置顺序跟 // ip中的顺序一致,而且随着处理而递增,所以为了保持中处理的一致性(这样 // 就可以保证许多地方使用一致的处理方式)。这里sp—就可以得到之前的处 // 理值,得到我们想要的结果了。 e->sp--; r->variables[code->index].len = e->sp->len; r->variables[code->index].valid = 1; r->variables[code->index].no_cacheable = 0; r->variables[code->index].not_found = 0; r->variables[code->index].data = e->sp->data;
(二) rewrite行的解析
有了前面对if和set的介绍,rewrite分析起来就简单多了,大多数的处理前面都已经出现过了,这里只说几个重点。
1. 在rewrite后面第二个参数中,凡是以“http://”,“https://”,“$scheme”,或者是最后的参数是“redirect”的,在后续的处理都是产生302的响应,其次最后的参数里,如“permanent”会产生301响应,last和break将会产生nginx内部的重定向(相当于重新做一次http的请求),会重新进行location的匹配等,但是它们的区别在于break在重定向时,就不会在做重定向的处理,而last将会继续。
2. 对于rewrite后面第二个参数的处理,跟处理set中第二个变量的过程是大致一样的。需要强调的有以下几点:
// 刚开始的时候已经给regex做了初始化,为什么后面有重新赋值了一次呢?原因就是“它可能变了”。。。
regex = ngx_http_script_start_code(cf->pool, &lcf->codes,
sizeof(ngx_http_script_regex_code_t));
…
regex = sc.main;
对于上面这个问题,可以追溯到ngx_http_script_add_code函数,我们知道nginx在对数组push时,如果发现空间不足会做扩充,在扩充时会分配原来两倍大小的空间,并把原有数据copy过来。这样一样原来执行这个数组中的一些指针值此时就不能再用了,而要指向他们的“新家”,这也就是这里regex重新赋值以及以®ex为参数传递给ngx_http_script_add_code的目的所在。
3. 到这里我们的if指令到了结束的时候,那么就以一个NULL来做结尾。
if (last) {
code = ngx_http_script_add_code(lcf->codes, sizeof(uintptr_t), ®ex);
if (code == NULL) {
return NGX_CONF_ERROR;
}
*code = NULL;
}
看最后这一句:
regex->next = (u_char *) lcf->codes->elts + lcf->codes->nelts - (u_char *) regex;
这里其实说明了当前位置距离codes数组的大小,这样在必要的时候,例如e->ip += code->next,跳过当前结构去处理下一个,很方便。
到这里,基本的轮廓就是这样子,至于各个handler的执行细节,大家可以自己去阅读代码,提示一点的是,要结合e->ip,e->sp等,ngx_http_script_engine_t结构中的一些成员来看,就容易懂了。
下一篇,我们针对脚本解析这块涉及的一些细节,做下探讨,来结束这块的分析