vislee / leevis.com

Blog
87 stars 13 forks source link

为什么说nginx的if是邪恶的 #130

Open vislee opened 7 years ago

vislee commented 7 years ago

概述

在nginx网站上有一篇帖子《If Is Evil》 该帖子描述了在if使用的过程中,如果你不是非常熟悉nginx的流程或者没有充分的测试,可能会发生一些你预想不到的情况。并给出了一些“邪恶的”实例。

下面,会通过代码结合实例分析一下if为什么是“邪恶的”。

代码分析

  1. if是通过rewrite模块提供的配置指令,添加到server rewrite阶段和rewrite阶段执行。post rewrite阶段也和rewrite模块有关。
  2. 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阶段重新执行。
  3. nginx http相关的配置分3个以上的级别(http{server{location / { if xxx {} ... }}}),部分指令是可以继承的,例如:client_body_buffer_size在server下配置了,server下所有未配置该指令的location都会继承server下的配置。部分指令不可以继承,如:rewrite模块的指令不会被继承。
  4. nginx中大多数配置指令是和顺序无关的,而rewrite模块的指令和配置顺序有关,例如:先配置set 再通过if判断,反过来就会发生错误。
  5. rewrite模块代码分析请看本仓库issues。
location /only-one-if {
    set $true 1;

    if ($true) {
        add_header X-First 1;
    }

    if ($true) {
        add_header X-Second 2;
    }

    return 204;
}

解析配置文件

  1. 首先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数组的下标。

  2. 第一个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配置。

  3. 第二个if指令添加元素,同上。

  4. return指令添加元素。添加ngx_http_script_return_code_t元素,回调函数code是ngx_http_script_return_code,回调参数status为204.

  5. 配置文件解析完后,最终会调用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数组中添加的回调。

  1. 执行set添加的回调ngx_http_script_value_code回调函数,把变量付给sp数组的第一个元素, 该数组类型为ngx_variable_value_t。接着执行ngx_http_script_set_var_code回调函数,把添加到sp中的元素的值赋值到r->variables数组index下标的元素。

  2. 执行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函数更新。

  3. 同上,执行ngx_http_script_var_code回调,再执行ngx_http_script_if_code回调,赋值location的上下文数组并调用ngx_http_update_location_config函数更新,覆盖了上一个if的配置信息。

  4. 执行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。