在nginx中变量的类型只有一种,字符串。
比如我们在nginx.conf中进行定义:
set $a "hello world"
我们使用了标准 ngx_rewrite 模块的 set 配置指令对变量 $a 进行了赋值操作。特别地,我们把字符串 hello world 赋给了它。
nginx变量名前面有 符号,这是nginx的要求,所有变量前边都必须要有 符号,类似于PHP。
这样做的好处在于我们可以直接把变量嵌入到字符串中:
set $a hello;
set $b "$a, $a";
#在上述代码中,则b变量为hello,hello
然后openResty 启动nginx ,
curl http://localhost/test
#输出:a:hello,world
我们使用第三方 ngx_echo 模块的 echo 配置指令将 $foo 变量的值作为当前请求的响应体输出。
在echo我们仍然可以进行变量插值,但是如何直接输入$符号,在nginx中我们没有办法对$符号进行转义。
不过我们可以通过没有办法进行变量插入的模块进行构造值为$的变量
geo $dollar {
default "$";
}
server {
listen 8080;
location /test {
echo "This is a dollar sign: $dollar";
}
}
比如我们可以通过上面的匹配来绕开,$无法转义的情况
用到了标准模块 ngx_geo 提供的配置指令 geo 来为变量$dollar 赋予字符串 “$”,这样我们在下面需要使用美元符的地方,就直接引用我们的 $dollar 变量就可以了。其实 ngx_geo 模块最常规的用法是根据客户端的 IP 地址对指定的 Nginx 变量进行赋值。
在特殊的情况变量插入的情况,变量与字符创需要进行分割:
server {
listen 8080;
location /test {
set $first "hello ";
echo "${first}world";
#在这种情况下变量与字符串没有分割,必须采用{}将变量与字符创进行分割
}
}
上面我们使用的set以及geo模块,如果变量不存在都会自动创建变量,但是nginx的变量创建与赋值发生在不同的阶段,当nginx启动的时候回根据配置文件进行变量创建,但是变量的赋值发生在请求实际处理的时候。
Nginx 变量一旦创建,其变量名的可见范围就是整个 Nginx 配置,甚至可以跨越不同虚拟主机的 server 配置块。
server {
listen 8080;
location /foo {
echo "foo = [$foo]";
}
location /bar {
set $foo 32;
echo "foo = [$foo]";
}
}
在上面的配置中,都定义了foo变量,因此配置检查可以通过,但是当我们访问:
$ curl 'http://localhost:8080/foo'
foo = []
$ curl 'http://localhost:8080/bar'
foo = [32]
$ curl 'http://localhost:8080/foo'
foo = []
我们发现,当我们第一次访问/foo的时候变量为空,然后访问/bar变量进行赋值因此是32,再一次访问/bar时,变量为空,但是在nginx中变量是全局的这不矛盾吗?嘿嘿,这里并不矛盾,nginx变量虽然是全局的,但是每个请求都会有自己独立的变量副本,请求与请求之间互不影响。
server {
listen 8080;
location /foo {
set $a hello;
echo_exec /bar;
}
location /bar {
echo "a = [$a]";
}
}
上面使用第三方模块 ngx_echo 提供的 echo_exec 配置指令,发起了到/bar的内部跳转。
curl localhost:8080/foo
a = [hello]
因此我们可以判断这是一个请求,不同于301、302,进行的跳转。但是如果我们直接访问/bar,那么我们会得到空的变量。
server {
listen 8080;
location /foo {
set $a hello;
rewrite ^ /bar;
}
location /bar {
echo "a = [$a]";
}
}
在上面的配置,我们依然采用的是内部跳转,使用ngx_rewrite模块。实现相同的功能。
结论:nginx中变量虽然时全局的,但是每个请求都会有自己的副本,互不影响,各个模块之间也不影响。
上面我们使用set指令创建的变量称为隐式变量或者用户自定义变量,在nginx核心以及各个模块中存在预定义变量。
Nginx 内建变量最常见的用途就是获取关于请求或响应的各种信息。例如由 ngx_http_core 模块提供的内建变量 $uri,可以用来获取当前请求的 URI(经过解码,并且不含请求参数),而 $request_uri 则用来获取请求最原始的 URI (未经解码,并且包含请求参数).
location /test {
echo "uri = $uri";
echo "request_uri = $request_uri";
}
curl 'http://localhost:8080/test'
uri = /test
request_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
另一个常用的内建变量是一个族群,我们称为args_XXX变量群:
location /test {
echo "name: $arg_name";
echo "class: $arg_class";
}
我们开始进行访问:
curl 'http://localhost:8080/test'
name:
class:
curl 'http://localhost:8080/test?name=Tom&class=3'
name: Tom
class: 3
curl 'http://localhost:8080/test?name=hello%20world&class=9'
name: hello%20world
class: 9
args_XXX实则就是拿取请求参数,同时注意XXX不区分大小写哦。
但是我们拿到的参数是经过编码的,name: hello%20world其中%20实则为空格,我们可以采用:第三方 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
这样我们便将url参数成功进行解码
类似 $arg_XXX 的内建变量还有不少,比如用来取 cookie 值的 $cookie_XXX 变量群,用来取请求头的 $http_XXX 变量群,以及用来取响应头的 $sent_http_XXX 变量群.
需要注意的是:
许多内建变量都是只读的,比如我们刚才介绍的 uri和 request_uri。
但是也有例外,对于args参数,就是可以被修改的:
location /test {
set $orig_args $args;
set $args "a=3&b=4";
echo "original args: $orig_args";
echo "args: $args";
}
curl 'http://localhost:8080/test'
original args:
args: a=3&b=4
curl 'http://localhost:8080/test?a=0&b=1&c=2'
original args: a=0&b=1&c=2
args: a=3&b=4
上面我们先把args赋值给original ,然后在修改args变量的值,需要特别指出的是,向args与args_XXX变量都不是实现进行计算好的,而是使用的时候对url进行扫描,然后进行赋值操作。
上面我们提到,在nginx中,args_XXX等变量都是在用到的时候,对请求的URL进行扫描,然后进行取值操作,那么如果我们多次用到这个值,每次都去取就会很浪费,因此存在变量缓存,Nginx 变量也可以选择将其值容器用作缓存,这样在多次读取变量的时候,就只需要调用“取处理程序”计算一次。
map $args $foo {
default 0;
debug 1;
}
server {
listen 8080;
location /test {
set $orig_foo $foo;
set $args debug;
echo "original foo: $orig_foo";
echo "foo: $foo";
}
}
再上面我们用到了标准的ngx_map模块的,map配置指令,map在此处我们理解为映射,或者理解为y=f(x),在上面我们认为args 为自变量x,foo 为因变量也就是y,y随着x的变化而变化。
映射规则:
default 默认匹配条件,还记得swich里面的default操作符吧,它们很相似,都是在其他条件不匹配的情况下,默认的匹配,将变量foo赋值为0。
debug 也就是当args为debug的时候,他们则匹配,然后将变量赋值foo为1
因此当args不是debug的情况下,foo都为0,否则为1。
curl 'http://localhost:8080/test'
original foo: 0
foo: 0
在访问中,匹配将args改为debug,然后在输入foo值,x变换y应该跟着变化,应该输出1才对。其实原因很简单,那就是 $foo 变量在第一次读取时,根据映射规则计算出的值被缓存住了。刚才我们说过,Nginx 模块可以为其创建的变量选择使用值容器,作为其“取处理程序”计算结果的缓存。显然, ngx_map 模块认为变量间的映射计算足够昂贵,需要自动将因变量的计算结果缓存下来,这样在当前请求的处理过程中如果再次读取这个因变量,Nginx 就可以直接返回缓存住的结果,而不再调用该变量的“取处理程序”再行计算了。
在nginx中我们将请求分为:
主请求:
HTTP 客户端从 Nginx 外部发起的请求。
子请求:
由 Nginx 正在处理的请求在 Nginx 内部发起的一种级联请求。
location /main {
echo_location /foo;
echo_location /bar;
}
location /foo {
echo foo;
}
location /bar {
echo bar;
}
在上面的配置中,http请求/main,然后利用第三方 ngx_echo 模块的 echo_location 指令分别发起到 /foo 和 /bar 这两个接口的 GET 类型的“子请求”执行是按照配置书写的顺序串行处理的,即只有当 /foo 请求处理完毕之后,才会接着处理 /bar 请求。这两个“子请求”的输出会按执行顺序拼接起来,作为 /main 接口的最终输出。
子请求”方式的通信是在同一个虚拟主机内部进行的,所以 Nginx 核心在实现“子请求”的时候,就只调用了若干个 C 函数,完全不涉及任何网络或者 UNIX 套接字(socket)通信。我们由此可以看出“子请求”的执行效率是极高的。
location /test {
set $a hello,world;
echo_location /bar;
echo $a;
}
location /bar {
echo $a;
set $a xixi;
}
在上述请求过程中,我们首相将a设置为hello,world,然后发送自请求到/bar中,打印a,然后在赋值为xixi,回到主请求再次打印a。
curl http://localhost/test
xixi
hello,world
我们发现,在主请求中对a进行赋值,但是在子请求中没有打印a的值,然后将a进行赋值为xixi,主请求依然打印hello,world。
因此我们得到结论:
主请求与子请求各自拥有自己的独立变量副本,互补影响。
特例:
不幸的是,这不是所有情况的结论,在ngx_auth_request 模块发起的“子请求”确实是与其“父请求”共享一套 Nginx 变量的值容器。
在nginx中变量,当我们声明了变量但是没有赋值,以及我们采用args_XXX访问变量变量不存在的情况下。
location /foo {
echo "foo = [$foo]";
}
location /bar {
set $foo 32;
echo "foo = [$foo]";
}
例如我们访问/foo,foo变量虽然在配置文件中被声明,但是并没有被赋值,我们插入nginx的error.log日志[warn] 5765#0: *1 using uninitialized “foo” variable, …
当 /foo 接口中的 echo 指令实际执行的时候,它会对它的参数 “foo = $foo]” 进行“变量插值”计算。于是,参数串中的 $foo 变量会被读取,而 Nginx 会首先检查其值容器里的取值,结果它看到了“不合法”这个特殊值,于是它这才决定继续调用$foo 变量的“取处理程序”。于是$foo 变量的“取处理程序”开始运行,它向 Nginx 的错误日志打印出上面那条警告消息,然后返回一个空字符串作为 $foo 的值,并从此缓存在 $foo 的值容器中.
细心的读者会注意到刚刚描述的这个过程其实就是那些支持值缓存的内建变量的工作原理,只不过 set 指令在这里借用了这套机制来处理未正确初始化的 Nginx 变量。值得一提的是,只有“不合法”这个特殊值才会触发 Nginx 调用变量的“取处理程序”,而特殊值“没找到”却不会
但是当我们采用args_XXX进行参数寻找的时候,如果url中没有相关参数,则认为为空字符,nginx直接忽略了这种情况,不过我们可以采用lua进行查找:
location /test {
content_by_lua '
if ngx.var.arg_name == nil then
ngx.say("name: missing")
else
ngx.say("name: [", ngx.var.arg_name, "]")
end
';
}
上面我们采用了ngx_lua模块, ngx_lua 模块可以让nginx直接运行lua语言,我们在 Lua 代码里引用 Nginx 变量都是通过 ngx.var 这个由 ngx_lua 模块提供的 Lua 接口。比如引用 Nginx 变量 $VARIABLE 时,就在 Lua 代码里写作 ngx.var.VARIABLE 就可以了。
不过,标准的 $arg_XXX 变量还是有一些局限,比如我们用下面这个请求来测试刚才那个 /test 接口:
curl 'http://localhost:8080/test?name'
name: missing
此时, argname变量仍然读出“找不到”这个特殊值,这就明显有些违反常识。此外, arg_XXX 变量在请求 URL 中有多个同名 XXX 参数时,就只会返回最先出现的那个 XXX 参数的值,而默默忽略掉其他实例:
curl 'http://localhost:8080/test?name=Tom&name=Jim&name=Bob'
name: [Tom]
要解决这些局限,可以直接在 Lua 代码中使用 ngx_lua 模块提供的 ngx.req.get_uri_args 函数。
https://openresty.org/download/agentzh-nginx-tutorials-zhcn.html