浏览 1.6k
前几篇文章,我们分别介绍了NGINX变量的基本特性和实现原理以及NGINX中复杂变量求值的原理。 本篇,我们继续分析NGINX中rewrite模块定义的系列指令比如if/set/break/return等的实现原理。
我们在分析NGINX复杂变量求值时,已经介绍和分析了NGINX脚本执行的基本原理。除了复杂变量求值涉及到NGINX脚本语言以外,另外一种显式地通过在配置脚本中配置的指令,比如NGINX 的rewrite模块或者geo模块定义的指令也需要NGINX的脚本语言功能的支持。
NGINX在启动阶段,会把一些指令比如set/rewrite/return等编译成一系列指令集,并且存放到每一个location中的ngx_http_rewrite_loc_conf_t结构体中。
typedef struct {
ngx_array_t *codes;//保存着所属location下的所有编译后脚本
ngx_uint_t stack_size;//变量值栈sp的大小
……
} ngx_http_rewrite_loc_conf_t;
这个结构其实就是rewrite模块在location级别下面的配置结构体。如果匹配的location下面没有脚本语言配置,则codes数组是空的,否则codes成员就会存放着解析后的脚本指令的结构体。这样每当一个HTTP请求到来时就可以在这个请求本身的上下文执行对应的指令。
这些指令对应的执行阶段是在HTTP的rewrite阶段,也就是在函数ngx_http_rewrite_handler中执行的。当函数执行时,首先创建一个ngx_http_script_engine_t结构,然后开始执行ngx_http_rewrite_loc_conf_t结构中codes数组中的指令。所以对应的代码的执行非常简单,只需要遍历和执行codes数组中的函数就可以。所以最重要的工作还是指令的编译和解析阶段,也就是如何把匹配的指令解析成一系列的指令结构体。
我们将以如下的配置指令为例来进行原理分析。
if ($a =‘test’){
set $a ${a} again;
return 200 $a;
}
上面的例子中设计到了if/set/return三个指令,一个复杂变量和一个常量值。
对应这些指令和变量元素对应的指令结构体分别是:
1. 变量常量字符串的结构体
typedef struct {
ngx_http_script_code_ptcode; //code指向获取变量字符串的方法
uintptr_tlen; //常量字符串长度
} ngx_http_script_var_code_t;
2. 编译变量值的结构体
typedef struct {
ngx_http_script_code_ptcode; //code指向获取变量值的脚本指令
/*外部变量值如果为整数,则转为整数后赋值给value,否则value为0*/
uintptr_tvalue;
uintptr_ttext_len; //外部变量值(set的第二个参数)的长度
uintptr_ttext_data; //外部变量值的起始地址
} ngx_http_script_value_code_t;
3. 编译复杂变量值的结构体
typedef struct {
ngx_http_script_code_pt code;//code指向编译复杂变量值的脚本指令方法
ngx_array_t*lengths; //lengths存放的是复杂变量值中内嵌变量的值长度
} ngx_http_script_complex_value_code_t;
4. 指令if的结构体
typedef struct {
ngx_http_script_code_ptcode;
uintptr_tnext;
void**loc_conf;
} ngx_http_script_if_code_t;
5. 指令set的结构体
根据set后面的变量是否有set_handler函数,可以分别使用如下两种指令结构体。
typedef struct {
ngx_http_script_code_pt code;
uintptr_tindex;
} ngx_http_script_var_code_t;
typedef struct {
ngx_http_script_code_pt code;
ngx_http_set_variable_pt handler;
uintptr_t data;
} ngx_http_script_var_handler_code_t;
6. 指令return的结构体
typedef struct {
ngx_http_script_code_pt code;
uintptr_t status;
ngx_http_complex_value_t text;
} ngx_http_script_return_code_t;
7. 指令if的结构体
typedef struct {
ngx_http_script_code_pt code;
uintptr_t next;
void **loc_conf;
} ngx_http_script_if_code_t;
在NGINX启动过程中,通过函数ngx_http_rewrite_if函数解析配置中的if指令。对应这个例子具体流程是:
1. 在解析if指令时,会把if当做location来处理,并且把这个if对应的location的type被设置成了noname,所以,在进行url匹配时并不会查找到此location。
2. 函数ngx_http_rewrite_if通过ngx_http_rewrite_if_condition来具体解析if指令中的条件。
a. 对于本例,首先解析到变量$a,这时通过函数ngx_http_rewrite_variable生成一个结构为ngx_http_script_var_code_t的指令结构体。结构体对应的处理函数是ngx_http_script_var_code,并且结构体也存了变量$a在变量数组中的index。
b. 然后解析到了后面的=号,此时通过函数ngx_http_rewrite_value添加了指令结构体ngx_http_script_value_code_t用来获取=号后面变量的值。函数ngx_http_rewrite_value本身可以处理复杂变量也就是变量嵌入的情况。对应于我们的例子中“test”是一个常量字符串,此时结构体ngx_http_script_value_code_t的指令回调函数是ngx_http_script_value_code,并且结构体也存储了常量字符串的数值和长度。
c. 解析完=号后面的变量以后,再为=号生成一个结构体ngx_http_script_code_pt,并且设置此结构体的指令回调函数是ngx_http_script_equal_code。
3. 指令if的条件解析完毕以后,再对if指令本身生成一个指令结构体ngx_http_script_if_code_t,结构体的指令回调函数是ngx_http_script_if_code。
至此,整个if语句本身已经解析完毕,解析完毕后,对应if所在的location的指令数组如下图所示:
4. 完整的if指令以及条件解析完毕以后,需要对if本身这个block中的指令进行解析。并且把解析后的指令存放到if_code结构体中的loc_conf中,并且调用函数ngx_conf_parse开始解析。
5. 现在开始解析if块中的set指令,对应的解析函数是ngx_http_rewrite_set。具体流程是:
a. 对于set后面的变量,分别通过函数ngx_http_add_variable以及函数ngx_http_get_variable_index把变量$a加入系统数组中,并且获得其对应的index。
b. 对于set指令后面的第二个参数${a}test,其自身是一个复杂变量。通过函数ngx_http_rewrite_value添加指令结构体ngx_http_script_complex_value_code_t,并且设置其指令回调函数为ngx_http_script_complex_value_code。
c. 通过ngx_http_script_compile生成对复杂变量${a}test求值的指令数组并且存放到指令结构体ngx_http_script_complex_value_code中的lengths数组中。
d. 对于set指令本身,生成指令结构体ngx_http_script_var_code_t并且设置其指令回调函数为ngx_http_script_set_var_code,并且把5a中生成的变量$a的index存放到此结构体中。
6. 接着解析if块中的return指令,对应的解析函数是ngx_http_rewrite_return。具体流程是:
a. 为return指令生成指令结构体ngx_http_script_return_code_t, 并且设置其指令回调函数为ngx_http_script_return_code。
b. 存储return指令后面的状态码到结构ngx_http_script_return_code中。
c. 把return的第二个参数,通过函数ngx_http_compile_complex_value存放到指令结构提中的text字段中。此text字段是ngx_http_complex_value_t类型,其本身存放这复杂变量求值的指令结构。
至此,if块中的所有指令都已经生成完毕,if本身的codes数组此时的内容如下。其中return指令结构体return中的text字段还存放这对于复杂变量求值的指令集:
在经过指令解析后,配置文件中的各种指令都被解析成各种指令回调函数。这些指令在rewrite模块的执行函数ngx_http_rewrite_handler中得到执行。具体逻辑非常简单就是循环执行指令数组中的指令。
在执行到if指令的回调函数ngx_http_script_if_code时,如果if的条件成立,则通过函数ngx_http_update_location_config把当前执行的context切换到if指令块中的指令数组,从而可以继续执行if块中的指令。
通过四篇系列文章分析了NGINX的变量以及脚本语言实现原理。这些变量和脚本语言的支持方便和丰富了NGINX的配置和使用。但是,这些NGINX自定义的脚本语言,在表达能力和功能上远不及现有流行的脚本语言比如Lua,JavaScript等。这也促成了Openresty和NJS的出现。这两种产品极大方便了用户使用脚本语言开发新功能,降低了编写模块的门槛。对NGINX本身的推广和方便使用也起到了极大的作用。
按点赞数排序
按时间排序