nginx 诡异的if指令—— 记 location 里 if指令 和 try_files 冲突问题排查

3,069 阅读2分钟

先看下if指令会出现的问题 原文地址:agentzh.blogspot.com/2011/03/how…

以下是翻译:

Nginx 的 if 指令在实践中确实有些奇怪。当人们对它的行为没有足够的了解时,他们可能会滥用它。在这篇文章中,我将在这里分析一些示例,以便人们可以了解并正确使用它。

简而言之,Nginx 的“if”块有效地创建了一个(嵌套的)位置块,一旦“if”条件匹配,只有内部位置块(即“if”块)的内容处理程序将被执行。

Case 1

  location /proxy {
      set $a 32;
      if ($a = 32) {
          set $a 56;
      }
      set $a 76;
      proxy_pass http://127.0.0.1:$server_port/$a;
  }

  location ~ /(\d+) {
      echo $1;
  }

调用 /proxy 会得到 76,因为它按以下步骤工作:

  1. Nginx 按照它们在配置文件中的顺序运行所有重写阶段指令,即,

    set $a 32;
    if ($a = 32) {
        set $a 56;
    }
    set $a 76;
    

$a 的最终值为 76。

  1. Nginx 陷入“if”内部块,因为它的条件 $a = 32 在步骤 1 中得到满足。
  2. 内部块没有任何内容处理程序,ngx_proxy继承了外部范围内的内容处理程序(ngx_proxy的处理程序)(参见src/http/modules/ngx_http_proxy_module.c:2025)。
  3. proxy_pass 指定的配置也被内部“if”块继承(参见 src/http/modules/ngx_http_proxy_module.c:2015)
  4. 请求终止(并且控制流永远不会超出“if”块)。

也就是说,外部作用域中的 proxy_pass 指令将永远不会在此示例中运行。真正为您服务的是“if”内部块。

让我们看看当我们用 下面例子 覆盖内部“if”块的内容处理程序时会发生什么: Case 2

  location /proxy {
      set $a 32;
      if ($a = 32) {
          set $a 56;
          echo "a = $a";
      }
      set $a 76;
      proxy_pass http://127.0.0.1:$server_port/$a;
  }

  location ~ /(\d+) {
      echo $1;
  }

您将在访问 /proxy 时获得此信息: a = 76 看起来违反直觉?哦,好吧,让我们看看这次发生了什么:

  1. Nginx 按照它们在配置文件中的顺序运行所有重写阶段指令,即,
 set $a 32;
     if ($a = 32) {
         set $a 56;
     }
     set $a 76;

$a 的最终值为 76。

  1. Nginx 陷入“if”内部块,因为它的条件 $a = 32 在步骤 1 中得到满足。

  2. 内部块确实有一个由“echo”指定的内容处理程序,然后 $a (76) 的值被发送到客户端。

  3. 请求终止(并且控制流永远不会超出“if”块),如案例 1。

我们确实可以选择让案例 2 按我们的意愿工作:

好的,你看到了 ngx_proxy 模块在嵌套位置之间的配置继承如何在这里发挥关键作用,并让你相信它可以按照你想要的方式工作。但是其他模块(例如我之前的一封电子邮件中提到的 echo)可能不会继承嵌套位置的内容处理程序(事实上,大多数内容处理程序模块,包括上游模块,都不会)。

在其他情况下,必须小心“if”块的配置继承的不良副作用,请考虑以下示例:

Case 4

  location /proxy {
      set $a 32;
      if ($a = 32) {
          return 404;
      }
      set $a 76;
      proxy_pass http://127.0.0.1:$server_port/$a;
      more_set_headers "X-Foo: $a";
  }

  location ~ /(\d+) {
      echo $1;
  }

在这里,ngx_header_more 的 more_set_headers 也将被“if”块创建的隐式位置继承。所以你会得到:

 $ curl localhost/proxy
  HTTP/1.1 404 Not Found
  Server: nginx/0.8.54 (without pool)
  Date: Mon, 14 Feb 2011 05:24:00 GMT
  Content-Type: text/html
  Content-Length: 184
  Connection: keep-alive
  X-Foo: 32

这可能是也可能不是你想要的:)

顺便说一句,在这种情况下 add_header 指令不会发出 X-Foo 标头,这并不意味着这里没有发生指令继承,但是 add_header 的标头过滤器将跳过 404 响应。

你看,幕后是多么的棘手!难怪人们一直吐槽...

如果你看懂上面的文章,自然下面的问题也会迎刃而解

location / {
          root /usr/local/var/www/;
           try_files $uri $uri/ /index.html;
         if ($host ~* .*.baidu.com)
           {
             add_header X-Frame-Options "sameorigin"
           }
     }

本来想做 前端的 浏览器路由模式,访问 test.baidu.com/main 或者test.baidu.com/login 页面 都是用try_files 进入到 test.baidu.com/index.html, 并添加X-Frame-Options 头信息,但是被if 无情的截断之后出现了404

综上所诉:

if, return, rewrite 和try_files 都可以触发nginx 的rewrite,请您使用他们的时候知道自己使用的什么!