leevis.com
leevis.com copied to clipboard
为什么说nginx的if是邪恶的
概述
在nginx网站上有一篇帖子《If Is Evil》 该帖子描述了在if使用的过程中,如果你不是非常熟悉nginx的流程或者没有充分的测试,可能会发生一些你预想不到的情况。并给出了一些“邪恶的”实例。
下面,会通过代码结合实例分析一下if为什么是“邪恶的”。
代码分析
- 在分析代码之前先了解以下知识:
- if是通过rewrite模块提供的配置指令,添加到server rewrite阶段和rewrite阶段执行。post rewrite阶段也和rewrite模块有关。
- nginx http的处理是分成11个阶段执行的(post read、server rewrite、find config、rewrite、post rewrite、preaccess、access、post access、precontent、content、log)。通常阶段是按照顺序执行的,例如:preaccess阶段生成的变量绝对不能在if中使用,因为if所在的阶段先执行。有时候会从post rewrite跳回到find config阶段重新执行。
- nginx http相关的配置分3个以上的级别(http{server{location / { if xxx {} ... }}}),部分指令是可以继承的,例如:client_body_buffer_size在server下配置了,server下所有未配置该指令的location都会继承server下的配置。部分指令不可以继承,如:rewrite模块的指令不会被继承。
- nginx中大多数配置指令是和顺序无关的,而rewrite模块的指令和配置顺序有关,例如:先配置set 再通过if判断,反过来就会发生错误。
- rewrite模块代码分析请看本仓库issues。
- 实例1分析:
location /only-one-if {
set $true 1;
if ($true) {
add_header X-First 1;
}
if ($true) {
add_header X-Second 2;
}
return 204;
}
解析配置文件
-
首先set指令在ngx_http_rewrite_loc_conf_t的codes数组中添加ngx_http_script_value_code_t元素,回调函数code为ngx_http_script_value_code,text_data 为变量预赋值。 接着向codes添加一个元素ngx_http_script_var_code_t,其回调函数code为ngx_http_script_set_var_code,回调函数参数index为变量在cmcf->variables数组的下标。
-
第一个if指令添加元素,先添加ngx_http_script_var_code_t元素,其回调函数为ngx_http_script_var_code,回调函数参数为变量在cmcf->variables数组的下标。接着添加ngx_http_script_if_code_t元素,其回调函数code为ngx_http_script_if_code,回调函数参数loc_conf指向该if对应的location的ctx,回调参数next指向本if结构添加在codes中元素的结尾(该赋值是在解析完location以后进行的,为的就是跳过该location所添加到code数组中的元素)。然后解析该location下的add_header配置。
-
第二个if指令添加元素,同上。
-
return指令添加元素。添加ngx_http_script_return_code_t元素,回调函数code是ngx_http_script_return_code,回调参数status为204.
-
配置文件解析完后,最终会调用ngx_http_rewrite_merge_loc_conf函数合并配置,而在rewrite模块并没有合并codes数组,而是在该数组中添加了个空指针作为结束标志。
请求处理
rewrite模块的handler函数被添加到server rewrite阶段和rewrite阶段。其handler的checker函数是ngx_http_core_rewrite_phase。
在该ngx_http_rewrite_handler函数中,如果codes为空,则立即返回。checker函数会执行该阶段的其他handler。而上述set、if、return等指令是配置在location中的,所以server rewrite阶段直接就返回了。
在rewrite阶段,会分配一个ngx_http_script_engine_t结构执行codes数组中添加的回调。
-
执行set添加的回调ngx_http_script_value_code回调函数,把变量付给sp数组的第一个元素, 该数组类型为ngx_variable_value_t。接着执行ngx_http_script_set_var_code回调函数,把添加到sp中的元素的值赋值到r->variables数组index下标的元素。
-
执行if添加的回调ngx_http_script_var_code,把变量赋值到sp数组的第一个元素(set执行两个回调函数执行完毕后,sp指针没有向后移动)。接着执行ngx_http_script_if_code回调,判断添加到sp的元素是否不为0。不为0则会把请求的location上下文数组赋值为对应if的location上下文数组,并调用ngx_http_update_location_config函数更新。
-
同上,执行ngx_http_script_var_code回调,再执行ngx_http_script_if_code回调,赋值location的上下文数组并调用ngx_http_update_location_config函数更新,覆盖了上一个if的配置信息。
-
执行return添加的ngx_http_script_return_code回调函数,赋值status为204。
至此,ngx_http_rewrite_handler回调返回204,rewrite阶段的checker函数调用ngx_http_finalize_request函数提前结束请求。
总结: return会跳过rewrite阶段后面log阶段前面的所有阶段的执行。 if执行如果没有break,则不会退出。后面的if会覆盖前面的if。