1. 设置变量
在 Nginx 配置中,变量只能存放一种类型的值,那就是字符串。Nginx中以$开头的字符串为变量对象。我们可以通过标准 ngx_http_rewrite_module 模块的 set 配置指令对变量进行赋值操作。
#nginx.conf配置文件
events
{
worker_connections 1024;
}
http
{
server
{
listen 8080;
location /test
{
set $foo hello; #使用set配置指令对变量$foo进行了赋值操作
echo "foo is:$foo"; #使用第三方echo-nginx-module模块配置指令将$foo变量的值作为当前请求的响应体输出,echo-nginx-module需要另外安装
}
}
}
#通过curl在命令行上请求/test这个接口
> curl http://192.168.1.131:8080/test
foo is:hello
2.变量作用域
- set 有两个含义: 创建变量,给变量赋值
- 创建变量是在加载配置时候
- 赋值变量是当请求处理上下文需要时触发
- 直接调用一个未被创建的变量,会导致配置无法启动
- 变量名是整个配置文件可见,但变量的值是基于每个独立请求的上下文
- set命令可以用在 server,location,if 配置上下文中
- 同一个上下文中,多次set同一个变量,使用配置中最后一个set值 (rewrite模块中return有额外影响)
#nginx.conf配置文件
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location /foo {
echo "foo is: $foo"; #而请求/foo接口时,我们总是得到空的 $foo值,因为用户变量未赋值就输出的话,得到的便是空字符串。
}
location /bar {
set $foo 32; #set 指令因为是在 location /bar中使用的,所以赋值操作只会在访问 /bar的请求中执行。
echo "foo is: $foo";
}
}
}
#通过curl在命令行上请求/foo这个接口
> curl http://192.168.1.131:8080/foo
foo is:
#通过curl在命令行上请求/bar这个接口
> curl http://192.168.1.131:8080/bar
foo is: 32
- Rewrite导致NGINX内部location跳转情形下,此时请求还属于同一个请求,因此变量值是不变的
#nginx.conf配置文件
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location /foo {
set $a hello;
rewrite ^ /bar; #用第三方模块echo-nginx-module的echo_exec /bar也能达到相同的效果
}
location /bar {
echo "a is: $a";
}
}
}
#通过curl在命令行上请求/foo这个接口
> curl http://192.168.1.131:8080/foo
a is: hello
3. 内置变量(预定义变量)
内置变量一般用于获取请求或响应的各种信息,由NGINX各模块自动产生。
常见的内置变量例如:
- 单个变量,例如$args,$uri, $request_uri
- 变量群变量, 例如$arg_parameter1, $arg_parameter2 ..
类似的变量群: $cookie_*, $http_* ,$upstream_http_*,$upstream_cookie_*,$upstream_trailer_*
#nginx.conf配置文件
events
{
worker_connections 1024;
}
http
{
server
{
listen 8080;
location /test
{
#单个变量
echo "uri = $uri";
echo "request_uri = $request_uri";
###变量群变量
echo "name: $arg_name";
echo "school: $arg_school";
}
}
}
#通过curl在命令行上请求/test这个接口,并加上相关参数
> curl http://192.168.1.131:8080/test?name=chengzw\&school=TU #加个\转义&
uri = /test
request_uri = /test?name=chengzw&school=TU
name: chengzw
school: TU
- 问题:内置变量是否可以重新赋值?
已索引类的变量(已索引的的变量,在NGINX内部有存放该变量值的容器),诸如$uri 等此类变量一般不能被重新赋值,假如尝试对变量$uri进行赋值:
set $uri urichange;
在Nginx启动的时候会报出如下错误
> nginx -s reload
nginx: [emerg] the duplicate "uri" variable in ......
未索引类变量(未索引指的是当用户调用的时候去启动一个内置程序去获取当前的值,并不提前预先索引好的),诸如$args, $arg_*, $cookie_*, $http_* 等可被重新赋值
#nginx.conf配置文件
events
{
worker_connections 1024;
}
http
{
server
{
listen 8080;
location /test
{
set $orig_name $arg_name; #先把内置变量$arg_name的值,即原始请求URL参数name的值,保存在用户变量$orig_name中
set $args "name=tom"; #然后通过对内建变量$args进行赋值,把当前请求的参数name改为tom
echo "original name is:$orig_name";
echo "name is:$arg_name"; #因为对内置变量$args的修改会直接导致当前请求的URL参数发生变化,因此内建变量$arg_name自然也会发生变化
}
}
}
#通过curl在命令行上请求/test这个接口,并加上相关参数
> curl http://192.168.1.131:8080/test?name=chengzw
original name is:chengzw
name is:tom
4. 变量与子请求
正常情况下,主子请求都应维护其自己的独立变量值,就像两个完全不同的请求 一样,父子请求之间的变量互不干扰。
在这个例子中,我们分别在/main,/foo 和 /bar 这三个 location 配置块中为同一名字的变量,$var,分别设置了不同的值并予以输出。特别地,我们在 /main 接口中,故意在调用过 /foo 和 /bar 这两个“子请求”之 后,再输出它自己的 $var 变量的值。显然/foo和/bar这两个子请求在处理过程中对变量$var各自所做的修改都丝毫没有影响到主请求/main。
#nginx.conf配置文件
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location /main {
set $var main;
echo_location /foo;
echo_location /bar;
echo "main is:$var";
}
location /foo {
set $var foo;
echo "foo is:$var";
}
location /bar {
set $var bar;
echo "bar is:$var";
}
}
}
#通过curl在命令行上请求/main这个接口
> curl http://192.168.1.131:8080/main
foo is:foo
bar is:bar
main is:main
但是有可能存在父请求使用子请求的变量 值情况,比如auth_request指令。
这里我们在 /main 接口中先为 $var 变量赋值 main,然后使用 ngx_http_auth_request_module模块提供的配置指令 auth_request,发起一个到 /sub 接口的子请求,最后利用 echo 指令输出变量 $var 的值。我们看到,/sub 接口对 $var 变量值的修改影响到了主请求 /main。 所以 ngx_http_auth_request_module模块发起的子请求确实是与其父请求共享一个Nginx 变量值容器。
#nginx.conf配置文件
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location /main {
set $var main;
auth_request /sub;
echo "main is:$var";
}
location /sub {
set $var sub;
echo "sub is:$var";
}
}
}
#通过curl在命令行上请求/main这个接口
> curl http://192.168.1.131:8080/main
main is:sub
- 问题:为什么自请求/sub的输出没有出现在最终的输出里呢?
因为auth_request指令会自动忽略自请求的响应体,只检查子请求的响应状态码。当状态码是2XX的时候,auth_request指令会忽略子请求而让Nginx继续处理当前的请求,否则它就会立即中断当前(主)请求的执行,返回相应的错误页。在我们的例子中,/sub子请求只是使用echo指令做了一些输出,所以隐式地返回了指示正常的200状态码。
5. 变量Mapping
Nginx的map指令可以用于定义两个Nginx变量之间的映射关系,或者说是函数关系。设置map映射,并不表示立即执行,只有当访问触发“结果变量”取值时,才会实际到map中去匹配查找。
下面这个例子中,我们用map指令定义了用户变量$foo与内置变量$args之间的映射关系。用数学函数的y=f(x)来说,$args就是自变量,$foo就是因变量,即$foo的值是由$args来决定的。
#nginx.conf配置文件
events {
worker_connections 1024;
}
http {
map $args $foo {
default 0; #default是一个特殊的匹配条件,在其他条件都不匹配时,这个条件才会匹配。当这个默认条件匹配时,就把因变量$\foo的值映射为0。default这个关键字是固定的。
debug 1; #如果自变量$args精确匹配到了debug这个字符串,则把因变量$foo的值映射为1。
}
server {
listen 8080;
location /test {
set $orig_foo $foo; #先把当前$foo变量的值保存在另一个用户变量$orig_foo中
set $args debug; #然后强行把$args的值改为debug
echo "original foo is:$orig_foo";
echo "foo is:$foo";
}
}
}
第一行输出显示$orig_foo的值为0,这是我们所期望的值,因为curl http://192.168.1.131:8080/test这个请求并没有提供URL参数,于是args最初的取值就是空,再根据我们先前定义的映射规则,$foo变量在第一次被读取时的值就应该为0(即默认匹配default条件)
第二行输出显示,在强行改写$args变量的值为字符串debug之后,$foo的值仍然为0,这显然不符合映射规则因为当$args值为debug时,$foo的值应该为1,这究竟是为什么呢?
原因就是在$foo变量在第一次读取时,根据映射规则计算出的值被缓存住了,这样在当前请求的处理过程中如果再次读取这个因变量,Nginx就会直接返回缓存住的结果,而不再调用该变量的"取处理程序"再进行计算了。
为了证实这一条,我们可以在请求中直接指定URL的参数为debug。我们可以看到现在$orig_foo的值就成了1,因为变量$foo在第一次被读取时,自变量$args的值就是debug,按照这个映射规则,"取处理程序"计算的返回值便是1。而后续再读取$foo的值时,就总是得到被缓存住的1这个结果,而不论$args后来变成什么样了。(在读取变量时执行的这段特殊代码,在 Nginx 中被称为"取处理程序"(get handler);而改写变量时执行的这段特殊代码,则被称为"存处理程序"(set handler))
#通过curl在命令行上请求/test这个接口
> curl http://192.168.1.131:8080/test
original foo is:0
foo is:0
#通过curl在命令行上请求/test这个接口,并带上debug参数
> curl http://192.168.1.131:8080/test?debug
original foo is:1
foo is:1
- map缓存强制刷新参数
1.11.7版本后增加了缓存控制参数 volatile,可以在map中增加volatile参数,来强制刷新缓存
events {
worker_connections 1024;
}
http {
map $args $foo {
volatile; #增加volatile参数,来强制刷新缓存
default 0;
debug 1;
}
server {
listen 8080;
location /test {
set $orig_foo $foo;
set $args debug;
echo "original foo is:$orig_foo";
echo "foo is:$foo";
}
}
}
可以看到由于第二次在读取$foo的时候,是重新调用该变量的"取处理程序"再进行计算,而不是使用第一次读取$foo时的值
#通过curl在命令行上请求/test这个接口
> curl http://192.168.1.131:8080/test
original foo is:0
foo is:1
6. 变量的两种特殊值
最开始我们提到过,Nginx变量的值只有一种类型,那就是字符串,但是变量也有可能压根就不存在有意义的值:一种是"不合法"(invalid),另一种是"没找到"(not found)。
举例说来:
- 当Nginx 用户变量 $foo 创建了却未被赋值时,$foo的值便是"不合法"
#nginx.conf配置文件
events {
worker_connections 1024;
}
http {
error_log /var/log/nginx/error-illigal.log warn; #指定error_log文件存放位置,这里等级要设置为warn才能看的到[warn] 26474#26474: *33 using uninitialized "foo" variable.....这个日志,如果等级高于warn是看不到的。
server {
listen 8080;
location /foo {
echo "foo is: $foo";
}
location /bar {
set $foo 32;
echo "foo is: $foo";
}
}
}
#通过curl在命令行上请求/foo这个接口
> curl 192.168.1.131:8080/foo
foo is:
从输出上看,未初始化的 $foo 变量("不合法")确实和空字符串的效果等同。但是对于上面这个请求,Nginx 的错误日志文件 error.log中多出一行类似下面这样的警告:
#查看error_log文件
cat /var/log/nginx/error-illigal.log
[warn] 26474#26474: *33 using uninitialized "foo" variable.....
问题:这一行警告是谁输出的呢?
答案是 set 指令为$foo 注册的"取处理程序"。当 /foo 接口中的echo指令实际执行的时候,它会对它的参数 "foo is: $foo" 进行“变量插值”计算。于是,参数串中的$foo 变量会被读取,而Nginx会首先检查其值容器里的取值,结果它看到了"不合法"这个特殊值,于是它这才决定继续调用$foo变量的"取处理程序"。于是$foo变量的"取处理程序"开始运行,它向Nginx的错误日志打印出上面那条警告消息,然后返回一个空字符串作为$foo的值,并从此缓存在$foo的值容器中。
- 如果当前请求的 URL 参数串中并没有提及 XXX 这个参数,则 $arg_XXX 内建变量的值便是“没找到”。
#nginx.conf配置文件
events {
worker_connections 1024;
}
http {
error_log /var/log/nginx/error-notfound.log debug;
server {
listen 8080;
location /test {
echo "name is: $arg_name";
}
}
}
我们看到,输出特殊值"没找到"的效果和空字符串是相同的。因为这一回是 Nginx 的“变量插值”引擎自动把"没找到"给忽略了。
> curl 192.168.1.131:8080/test
name is:
- 无论是“不合法”也好,还是"没找到"也罢,这两种Nginx变量所拥有的特殊值,和空字符串('')这种取值是完全不同的,比如JavaScript语言中也有专门的 undefined和null这两种特殊值,而Lua 语言中也有专门的nil值。
通过第三方模块lua-nginx-module,可以把"不合法"和"没找到"这两个特殊值与空值区分开
注:第三方模块需要另外安装,如果想省事可以直接安装OpenResty。OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
)
#nginx.conf配置文件
events {
worker_connections 1024;
}
http {
server {
listen 8080;
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 ';
}
}
}
> curl http://192.168.1.132:8080/test #"不合法"或"没找到"
name: missing
> curl http://192.168.1.132:8080/test?name= #name没输入便是空值
name: []
> curl http://192.168.1.132:8080/test?name=chengzw #name有输入值
name: [chengzw]