关于nginx变量的另一个常见误区是认为变量容器的生命周期,是与location配置块绑定的。其实不然,我们来看一个涉及“内部跳转”的例子:
server{
listen 8080;
location /foo {
set $a hello;
echo_exec /bar;
}
location /bar {
echo "a = [$a]";
}
}
这里我们在location /foo中,使用第三方模块ngx_echo提供的echo_exec 配置指令,发起到location /bar的“内部跳转”,就是在处理请求的过程中,于服务器内部,从一个location跳转到另一个location的过程。这不同于利用HTTP状态码内301和302所进行的“外部跳转”,因为后者是由HTTP客户端配合进行跳转的,而在客户端,用户可以通过浏览器地址栏这样的界面,看到请求的URL地址发生了变化。内部跳转和bourne shell(或bash)中的exec命令很像,都是“有去无回”。另一个相近的例子是C语言的goto语句。
$ curl localhost:8080/foo
a = [hello]
如果我们从客户端直接访问/bar接口,就会得到空的$a变量的值,因为它依赖于location /foo来对$a进行初始化。
从上面的这个例子我们看到,一个请求在其处理过程中,即使经历多个不同的location配置块,它使用的还是同一套nginx变量的副本。这里,我们也首次涉及到了“内部跳转”这个概念。值得一提的是,标准的ngx_rewrite模块rewrite配置指令其实也可以发起“内部跳转”,例如上面的那个例子用rewrite配置指令可以改写成下面的这样形式:
server{
listen 8080;
location /foo{
set $a hello;
rewrite ^ /bar;
}
location /bar {
echo"a = [$a]";
}
}
其效果和使用echo_exec是完全一样的。后面我们还专门介绍了这个rewrite指令的更多用法,比如发起301和302这样的“外部跳转”。
从上面这个例子我们看出,nginx变量值容器的生命周期是与当前正在处理的请求绑定的,而与location无关。
前面我们接触到的都是通过set指令隐式创建的nginx变量。这些变量我们一般称之为“用户自定义变量”,或者更简单一点“用户变量”。既然有“用户自定义变量”,自然也就有由nginx模块提供的“预定义变量”,或者称“内建变量”。
nginx内建变量最常见的用途就是获取关于请求或响应的各种信息。例如由ngx_http_core模块提供的内建变量$uri,可以用来获取当前请求的URI,而$requet_uri则用来获取请求最原始的URI。请看下面例子:
location /test {
echo "uri = $uri";
echo "request_uri = $request_uri";
}
这里为了简单起见,连server配置块也省略了,和前面所有实例一样,我们所监听的依然式8080端口。在这个例子里,我们打$uri和$request_uri的值输出到响应体中去。虾米那我们用不同的请求来测试一下这个/test接口:
$curl 'http://localhost:8080/test'
uri = /test
requset_uri = /test
$curl 'http://localhost:8080/test?a=3&b=4'
uri = /test
request_uri = /test?a=3&b=4
$curl 'http://localhost:8080/test/hello%20world?a=3&b=4'
uri = /test/hello world
request_uri = /test/hello%20world?a=3&b=4
另一个特别常用的内建变量其实并不是我们单独一个变量,而是有无线多种变量的一群变量,即名字以arg_开头的多有变量,我们姑且称之为$arg_xxx 变量群。一个例子是$arg_name,这个变量的值是当前请求名为name的URI参数的值,而且还是未解码的原始形式的值,我们来看一个比较完整的例子:
location /test{
echo "name:$arg_name";
echo"class:$arg_class";
}
然后在命令行上使用各种参数组合去请求这个/test接口:
$ curl 'http://localhost:8080/test'
name:
class:
$ curl ‘http://localhost:8080/test?test?name=Tom&class=3'
name:Tom
class:3
$ curl 'http://localhost:8080/test?name=hello%20world&class=9'
name:hello%20world
class:9
其实$arg_name 不仅可以匹配name参数,也可以匹配NAME参数,抑或是Name,等等:
$ curl 'http://localhost:8080/test?NAME=Marry'
name:Marry
class:
$ curl 'http://localhost:8080/test?Name=Jimmy'
name:Jimmy
class:
nginx会在匹配参数名之前,自动把原始请求中的参数名调整为全部小写形式。
如果你想对URI参数值中的%xx这样的编码序列进行解码,可以使用第三方ngx_set_misc模块提供的set_unescape_uri配置指令:
location /test {
set_unescape_uri $name $arg_name;
set_unescape_uri $class $arg_class;
echo "name:$name";
echo "class:$class";
}
现在我们再来看一下效果:
$curl 'http://localhost:8080/test?name=hello%20world&class=9'
name:hello world
class:9
空格果然被解码出来了!