引子
上一篇 分析了 rewrite 模块的实现和工作流程,这一篇我们来解释一下为什么 "if is evil" 中的例子会造成 “不合常理” 的结果。
下面针对每一个例子,我们先把配置片断完整列出来。然后,再给出目前它在 Nginx 中的 状态。最后,如果文字无法很好解释清楚,我们再从代码上对该例子进行分析。
例 1
# only second header will be present in respons
# not really bug, just how it works
location /only-one-if {
set $true 1;
if ($true) {
add_header X-First 1;
}
if ($true) {
add_header X-Second 2;
}
return 204;
}
-
依然和原文描述一致。它属于 Nginx 的固有工作模式。
-
原因:
-
add_header
指令是ngx_http_headers_module
模块 (output filter module) 提供的指令,这个指令基本算是工作在 CONTENT 阶段。 -
REWRITE 阶段的指令执行完后,请求才会进入下一个阶段。那两个
if
指令 的 condition 都是true
,那么最后一个if {}
会成为请求关联的 location 作用域。于是,进入 CONTENT 阶段后,第二个if {}
中的add_header
指 令才会生效。
-
例 2
# request will be sent to backend without uri changed
# to '/' due to if
location /proxy-pass-uri {
proxy_pass http://127.0.0.1:8080/;
set $true 1;
if ($true) {
# nothing
}
}
-
Nginx BUG, 因为最新稳定版 1.8.0 已经修正了:ChangeLog
-
大致原因就是因为往
if {}
创建的无名location {}
命并配置时,漏了一个变量plcf->location
。 -
另外,值得提起的一个点是:
proxy_pass
是一个 action directive, 按照定义和其它嵌套location
的实现来看,这个指令不应该被子作用域继承,但是if {}
当作子作用域的话,貌似是个例外,原因如下:-
proxy_pass
,fastcgi_pass
等指令提供了 location handler。一般情况下, Nginx 开始在 FIND_CONFIG 阶段处理请求时,因为要为请求匹配新的location {}
, 在ngx_http_core_find_config_phase
函数里先将r->content_handler
置NULL
,匹配成功后,如果匹配到的location {}
提供了 location handler, 再次在ngx_http_update_location_config
函数中将r->content_handler
赋与 新值:/* ngx_http_proxy_pass */ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_proxy_handler; /* ngx_http_core_find_config_phase */ r->content_handler = NULL; /* ngx_http_update_location_config */ if (clcf->handler) { r->content_handler = clcf->handler; }
-
由上一篇的分析我们能看到,如果
if
指令后的 conditon 成立,在命令函数ngx_http_script_if_code
中只调用了ngx_http_update_location_config
函数 而并没有让请求重新进入 FIND_CONFIG 阶段。这样一来,r->content_handler
的值就得以保留。 -
这也算是
if
的实现带来的 “副” 作用吧;
-
例 3
# try_files won't work due to if
location /if-try-files {
try_files /file @fallback;
set $true 1;
if ($true) {
# nothing
}
}
-
Nginx 固有逻辑
-
原因:首先,
try_files
不会被子作用域继承。其次,try_files
工作于 Nginx 内部定义的 TRY_FILES 阶段,并且此阶段优先级低于 REWRITE。所以,当if
指令的 condition 成立时,请求和if
创建的无名作用域关联,而此无名作用 域又不会继承try_files
相关配置。
例 4
# nginx will SIGSEGV
location /crash {
set $true 1;
if ($true) {
fastcgi_pass 127.0.0.1:9000;
}
if ($true) {
# no handler here
}
}
- Nginx BUG,目前稳定版本 1.2.0 已修正:Changelog
- 原因:参见 ChangeLog。
例 5
# alias with captures isn't correctly inherited into implicit nested
# location created by if
location ~* ^/if-and-alias/(?<file>.*) {
alias /tmp/$file;
set $true 1;
if ($true) {
# nothing
}
}
- Nginx BUG, 目前在稳定版 1.8.0 已修正: Changelog
- 原因:参见 ChangeLog。
小结
"if is evil" 一文中出现的例子要么已经被修正了,要么就是确实不能那么用。但是,
原文不是所有 if
带来的问题大全,兴许某一天,你自己就会碰见。希望能通过这两
篇的讲解,将来某一天碰到莫名的问题时,你能自己分析原因,说不定还能找到 Nginx
的 BUG 呢。
总之,安全驾驶,有备无患:
- Please make sure you actually do understand how it works.
- Do proper tesing.
Comments
不要轻轻地离开我,请留下点什么...