上一篇 分析了 rewrite 模块的实现和工作流程,这一篇我们来解释一下为什么 " if is evil " 中的例子会造成 “不合常理” 的结果。
下面针对每一个例子,我们先把配置片断完整列出来。然后,再给出目前它在 Nginx 中的 状态。最后,如果文字无法很好解释清楚,我们再从代码上对该例子进行分析。
# 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
指 令才会生效。
# 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
的实现带来的 “副” 作用吧;
# 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
相关配置。
# nginx will SIGSEGV location /crash { set $true 1; if ($true) { fastcgi_pass 127.0.0.1:9000; } if ($true) { # no handler here } }
# 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 } }
"if is evil" 一文中出现的例子要么已经被修正了,要么就是确实不能那么用。但是, 原文不是所有 if
带来的问题大全,兴许某一天,你自己就会碰见。希望能通过这两 篇的讲解,将来某一天碰到莫名的问题时,你能自己分析原因,说不定还能找到 Nginx 的 BUG 呢。
总之,安全驾驶,有备无患:
Category:Nginx Tagged:c/c++ notes nginx