nginx配置文件实际上就是一个个的小程序,nginx的配置文件使用的就是一门微型的编程语言,既然是编程语言,也就少不了“变量”。说白了,变量就是存放“值”的容器。所谓“值”,在许多的编程语句里面,既可以是3.14这样的数值,也可以是hello,worid这样的字符串,甚至可以是像数组、哈希表这样的复杂数据结构。然而,在nginx配置中,变量只能存放一种类型的值,因为也只有存在这样一种类型的值,那就是字符串。
比如我们的nginx.conf文件中有下面一行配置:
set $a "hello world";
我们使用了标准的ngx_rewrite模块的set配置指令对变量$a进行了赋值操作。特别地,我们把字符串hello world赋给了它。我们看到了,nginx变量名前面有一个$符号,这个是记法的要求。所有的nginx变量在nginx配置文件中引用都必须带上$前缀。这种表示方法和perl、php这些语言是相识的。
虽然$这样的变量前缀会让正统的java和c#程序员感到不舒服,但是这样的表示方法的好处也是显而易见的,那就是可以直接把变量嵌入刀字符串常量中以构造新的字符串:
set $a hello;
set $b "$a,$a";
这里我们通过已有的nginx变量$a的值,来构造变量$b的值,于是这两条指令顺序执行完之后,$a的值是hello,而$b的值是hello,hello。这种技术在perl世界里被称为”变量插值“(variable interpolation),它让专门的字符串拼接运算符变得不再那么必要。
实例
server{
listen 8080:
location /test{
set $foo hello;
echo "foo: $foo";
}
}
这个例子省略了nginx.conf配置文件中最外围的http配置以及events配置块。使用curl这个http客户端在命令行上请求这个/test接口,我们可以得到
$curl ‘http://localhost:8080/test'
foo: hello
这个我们使用第三方ngx_echo模块的echo配置指令将$foo变量的值作为当前请求的响应体输出。
我们看到,echo配置指令的参数也支持”变量插值“。不过,需要说明的是,并非所有的配置指令都支持”变量插值“。事实上,指令参数是否允许”变量插值“,取决于该指令的实现那模块。
如果我们想通过echo指令直接输出含有($)的字符串,那么是不可能的。不过幸运的是,我们可以绕过这个限制,比如通过不支持”变量插值“的模块配置指令专门构造出取值为$的nginx变量,然后再在echo中使用这个变量。看下面的例子:
geo $dollar{
default "$";
}
server {
listen 8080;
location 8080;
location /test {
echo "this is a dollar sign:$dollar";
}
}
测试结果如下:
$ curl 'http://localhost:8080/test'
this is a dollar sign : $
这里用到了标准模块ngx_geo提供的配置指令geo来为变量$dollar赋予字符串”$",这样我们在下面需要是使用$符号的地方,就直接引用我们的$dollar变量就行了。其实ngx_geo模块最常规的用法是根据客户端的ip地址对指定的nginx变量进行赋值。这里只是借用了它用来“无条件地”对我们的$dollar变量赋予“美元符号”这个值。
在“变量插值”的上下文中,还有一种特殊情况,即当引用的变量名之后紧跟着变量名的构成字符时(比如后跟字母、数字、下划线),我们就需要使用特别的记法来消除歧义,例如:
server{
listen 8080;
location /test{
set $first "hello";
echo "${first}world";
}
}
这里,我们在echo配置指令的参数值中引用变量$first的时候,后面紧跟着world这个单词,所以如果直接写作$firstworld则nginx“变量插值计算引擎将会识别变量$firstworld.为了解决这个难题,nginx的字符串记法支持使用花括号在$之后把变量名围起来,比如这里的${first}.上面的例子输出就是:
$curl 'http://localhost:8080/test
hello world
set指令(以及前面提到的geo指令)不仅有赋值的功能,它还有创建nginx变量的副作用,即当作为赋值对象的变量尚不存在,它会自动创建该变量。比如在上面这个列子中,如果$a这个变量尚未创建,则set指令会自动创建$a这个用户变量。如果我们不创建就直接使用它的值,则会报错。例如
server{
listen 8080;
location /bad {
echo $foo;
}
}
此时,nginx服务器就会拒绝加载配置:
【emerged】unkown "foo" variable
nginx变量的创建和赋值操作发生在全然不同的时间阶段。nginx变量的创建只能发生在nginx配置加载的时候,或者说nginx启动的时候;而赋值操作则只会发生在请求实际处理的时候。这意味着不创建而直接使用变量会导致nginx启动失败,同时也意味着我们无法在请求动态地创建新的nginx变量。
nginx变量一旦创建,其变量名的可见范围就是整个nginx配置,甚至可以跨越不同的虚拟主机的server配置块。我们来看一个例子:
server{
listen 8080;
location /foo{
echo "foo = [$foo]";
}
location /bar {
set $foo 32;
echo "foo = [$foo];
}
}
这里我们在location /bar中用set指令创建了变量$foo,于是在整个配置文件中这个变量都是可见的,因此我们可以在location /foo中直接引用这个变量而不用担心nginx会报错。
下面在命令行上用curl工具来访问这两个街斗的结果:
$curl 'http://localhost:8080/foo'
foo = []
$curl 'http://localhost:8080/bar'
foo = [32]
$furl 'http://localhost:8080/foo'
foo = []
这个例子我们可以看到,set指令因为在location /bar中使用的,所以赋值操作只会在访问/bar的请求中执行。而请求/foo接口的时候,我们总是得到空的$foo值,因为用户在未赋值就输出的话,得到的便是空字符串。
这个例子我们可以窥见的另一个重要特性是,nginx变量名的可见范围虽然是整个配置,但是每个请求都有多有变量的独立副本,或者说各变量用来存放值的容器的独立副本,但彼此互不干扰。比如前面我们请求了/bar接口后,$foo变量被赋予了值32,但它丝毫不会影响到后续对/foo接口对应的$foo 值(它仍然是空),因为各个请求都有自己独立的$foo变量的副本。
对于nignx新手来说,最常见的错误之一,就是将nignx变量理解成某种在请求之间全局共享的东西,或者说"全局变量”。而事实上,nginx变量的生命周期是不可能跨越请求边界的。