转载

Nginx 的 if 指令为何 "evil"(二)

Nginx 源代码笔记 - evil if [2]

Thu 30 July 2015

引子

上一篇 分析了 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_handlerNULL ,匹配成功后,如果匹配到的 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.

Category:Nginx Tagged:c/c++ notes nginx

Comments

正文到此结束
Loading...