纯真年代

阅读、体验、沉淀...

Nginx 踩坑记(一)

2014-04-26
#nginx

1. 预备知识

Nginx 有两种定义变量的方法,一种是在模块中定义,从而成为内建变量;另一种是在配置文件中通过 set 指令来定义。

配置过 Nginx 的同学可能都知道这样一个事实:一个请求在其处理过程中,即使经历多个不同的 location 配置块,它使用的还是同一套 Nginx 变量的副本。

例如,如果有如下的配置:

location /test1 {  
        set $hello hello;  
        rewrite /test1 /test2 last;

}
  
location /test2 {  
        echo $hello;  
}  

虽然变量是定义在 location /test1 中,但 Nginx 变量一旦创建,其变量名的可见范围就是整个 Nginx 配置,所以在 /test2 中直接使用该变量是不会报错的。另一方面,由于变量的生命周期是与请求相关的,所以如果直接访问 /test2,得到的变量 $hello 是一个空值,而访问 /test1,则会内部跳转到 /test2,将 /test1 中赋值的 hello 打印出来。

测试如下所示:

nginx-1.png

2. 如果变量定义在 server处

例如,如果有如下的配置:

set $hello hello;  
location /test1 {  
        echo $hello;  
}  
  
location /test2 {  
        echo $hello;  
}  

那么很容易想到,无论是访问 /test1 还是 /test2,都会输出在上层定义的变量 $hello 的值,测试如下:

nginx-2.png

再进一步,假设配置如下:

set $hello hello;  
location /test1 {  
        set $hello bye;  
        rewrite /test1 /test2 last;  
}  
  
location /test2 {  
        echo $hello;  
}  

则同样很容易想到,如果直接访问 /test1,则会先把在 server 段定义的 $hello 变量由 “hello” 改写为“bye”,再进行内部跳转到 /test2后,则会输出新的修改过后的变量 $hello;而如果直接访问 /test2,则会输出在 server 段定义的变量。测试如下:

nginx-3.png

3. 坑来了

到现在为止,一切都很正常,上述的知识点也是 Nginx 配置中的基础知识。但当和 ngx_lua 模块结合起来时,就时常会碰到比较诡异的事情。

假设配置如下:

set $hello hello;  
  
location /test1 {  
        content_by_lua '  
                ngx.var.hello = "bye";  
                ngx.exec("/test2");  
        ';  
}  
  
location /test2 {  
        echo "in test2";  
        echo $hello;  
}  

我们来猜猜分别访问 /test1 和 /test2,会输出什么。首先,server 段已经定义了变量 $hello 为字符串“hello”,直接访问 /test1 时,会将其修改为“bye”,然后执行 ngx.exec 跳转到 /test2,输出变量 $hello。但经过测试,分别访问 /test1 和 /test2 的时候,结果如下所示:

nginx-4.png

是不是很奇怪?直接访问 /test2 输出 hello 是正常的,可是访问 /test1 居然也是输出hello,在 /test1 中对hello变量的更改似乎没起作用。

到底 /test1 的 content_by_lua 段中,对变量 $test 的修改起作用没,我们可以测试一下,添加一行调试代码如下:

set $hello hello;  
  
location /test1 {  
        content_by_lua '  
                ngx.var.hello = "bye";  
                ngx.log(ngx.ERR, ngx.var.hello);  
                ngx.exec("/test2");  
        ';  
}  
  
location /test2 {  
        echo "in test2";  
        echo $hello;  
}  

再次访问 /test1,我们会发现,在 log 中记录下来了此时的 $hello 变量为“bye”。可是为什么执行内部跳转到 /test2 后,输出的结果还是“hello”呢?

我们再次改造一下配置:

location /test1 {  
        set $hello hello;  
        content_by_lua '  
                ngx.var.hello = "bye";  
                ngx.log(ngx.ERR, ngx.var.hello);  
                ngx.exec("/test2");  
        ';  
}  
  
location /test2 {  
        echo "in test2";  
        echo $hello;  
}  

和上面的区别在于,这次将变量的定义从 server 段迁到了 location /test1 中,在 /test1 中的 content_by_lua 段对其进行修改,再执行内部跳转到 /test2。测试结果如下:

nginx-5.png

这次的结果显然是符合我们的预期的。直接访问 /test1,由于在 content_by_lua 中将 $hello 变量更改为“bye”了,所以当内部跳转到 /test2 时,输出 $hello 变量就会输出“bye”;如果直接访问 /test2,则由于此时并没有对 $hello 变量赋值,所以 $hello 变量为空。


关于这个问题的解释,请见博文春哥关于 Nginx 踩坑记(一)的回复