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 打印出来。
测试如下所示:
2. 如果变量定义在 server处
例如,如果有如下的配置:
set $hello hello;
location /test1 {
echo $hello;
}
location /test2 {
echo $hello;
}
那么很容易想到,无论是访问 /test1 还是 /test2,都会输出在上层定义的变量 $hello
的值,测试如下:
再进一步,假设配置如下:
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 段定义的变量。测试如下:
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 的时候,结果如下所示:
是不是很奇怪?直接访问 /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。测试结果如下:
这次的结果显然是符合我们的预期的。直接访问 /test1,由于在 content_by_lua 中将 $hello
变量更改为“bye”了,所以当内部跳转到 /test2 时,输出 $hello
变量就会输出“bye”;如果直接访问 /test2,则由于此时并没有对 $hello
变量赋值,所以 $hello
变量为空。
关于这个问题的解释,请见博文春哥关于 Nginx 踩坑记(一)的回复。