Nginx 变量漫谈(五)

前面在 (二) 中我们已经了解到变量值容器的生命期是与请求绑定的,但是我当时有意避开了“请求”的正式定义。大家应当一直默认这里的“请求”都是指客户端发起的 HTTP 请求。其实在 Nginx 世界里有两种类型的“请求”,一种叫做“主请求”(main request),而另一种则叫做“子请求”(subrequest)。我们先来介绍一下它们。

所谓“主请求”,就是由 HTTP 客户端从 Nginx 外部发起的请求。我们前面见到的所有例子都只涉及到“主请求”,包括 (二) 中那两个使用 echo_exec 和 rewrite 指令发起“内部跳转”的例子。

而“子请求”则是由 Nginx 正在处理的请求在 Nginx 内部发起的一种级联请求。“子请求”在外观上很像 HTTP 请求,但实现上却和 HTTP 协议乃至网络通信一点儿关系都没有。它是 Nginx 内部的一种抽象调用,目的是为了方便用户把“主请求”的任务分解为多个较小粒度的“内部请求”,并发或串行地访问多个 location 接口,然后由这些 location 接口通力协作,共同完成整个“主请求”。当然,“子请求”的概念是相对的,任何一个“子请求”也可以再发起更多的“子子请求”,甚至可以玩递归调用(即自己调用自己)。当一个请求发起一个“子请求”的时候,按照 Nginx 的术语,习惯把前者称为后者的“父请求”(parent request)。值得一提的是,Apache 服务器中其实也有“子请求”的概念,所以来自 Apache 世界的读者对此应当不会感到陌生。

下面就来看一个使用了“子请求”的例子:

    location /main {        echo_location /foo;        echo_location /bar;    }     location /foo {        echo foo;    }     location /bar {        echo bar;    }

这里在 location /main 中,通过第三方 ngx_echo 模块的 echo_location 指令分别发起到 /foo 和 /bar 这两个接口的 GET 类型的“子请求”。由 echo_location 发起的“子请求”,其执行是按照配置书写的顺序串行处理的,即只有当 /foo 请求处理完毕之后,才会接着处理 /bar 请求。这两个“子请求”的输出会按执行顺序拼接起来,作为 /main 接口的最终输出:

    $ curl ‘http://localhost:8080/main‘    foo    bar

我们看到,“子请求”方式的通信是在同一个虚拟主机内部进行的,所以 Nginx 核心在实现“子请求”的时候,就只调用了若干个 C 函数,完全不涉及任何网络或者 UNIX 套接字(socket)通信。我们由此可以看出“子请求”的执行效率是极高的。

回到先前对 Nginx 变量值容器的生命期的讨论,我们现在依旧可以说,它们的生命期是与当前请求相关联的。每个请求都有所有变量值容器的独立副本,只不过当前请求既可以是“主请求”,也可以是“子请求”。即便是父子请求之间,同名变量一般也不会相互干扰。让我们来通过一个小实验证明一下这个说法:

    location /main {        set $var main;         echo_location /foo;        echo_location /bar;         echo "main: $var";    }     location /foo {        set $var foo;        echo "foo: $var";    }     location /bar {        set $var bar;        echo "bar: $var";    }

在这个例子中,我们分别在 /main/foo 和 /bar 这三个 location 配置块中为同一名字的变量,$var,分别设置了不同的值并予以输出。特别地,我们在 /main 接口中,故意在调用过 /foo 和 /bar 这两个“子请求”之后,再输出它自己的 $var 变量的值。请求 /main 接口的结果是这样的:

    $ curl ‘http://localhost:8080/main‘    foo: foo    bar: bar    main: main

显然,/foo 和 /bar 这两个“子请求”在处理过程中对变量 $var 各自所做的修改都丝毫没有影响到“主请求” /main. 于是这成功印证了“主请求”以及各个“子请求”都拥有不同的变量 $var 的值容器副本。

不幸的是,一些 Nginx 模块发起的“子请求”却会自动共享其“父请求”的变量值容器,比如第三方模块ngx_auth_request. 下面是一个例子:

    location /main {        set $var main;        auth_request /sub;        echo "main: $var";    }     location /sub {        set $var sub;        echo "sub: $var";    }

这里我们在 /main 接口中先为 $var 变量赋初值 main,然后使用 ngx_auth_request 模块提供的配置指令auth_request,发起一个到 /sub 接口的“子请求”,最后利用 echo 指令输出变量 $var 的值。而我们在/sub 接口中则故意把 $var 变量的值改写成 sub. 访问 /main 接口的结果如下:

    $ curl ‘http://localhost:8080/main‘    main: sub

我们看到,/sub 接口对 $var 变量值的修改影响到了主请求 /main. 所以 ngx_auth_request 模块发起的“子请求”确实是与其“父请求”共享一套 Nginx 变量的值容器。

对于上面这个例子,相信有读者会问:“为什么‘子请求’ /sub 的输出没有出现在最终的输出里呢?”答案很简单,那就是因为 auth_request 指令会自动忽略“子请求”的响应体,而只检查“子请求”的响应状态码。当状态码是 2XX 的时候,auth_request 指令会忽略“子请求”而让 Nginx 继续处理当前的请求,否则它就会立即中断当前(主)请求的执行,返回相应的出错页。在我们的例子中,/sub “子请求”只是使用 echo指令作了一些输出,所以隐式地返回了指示正常的 200 状态码。

如 ngx_auth_request 模块这样父子请求共享一套 Nginx 变量的行为,虽然可以让父子请求之间的数据双向传递变得极为容易,但是对于足够复杂的配置,却也经常导致不少难于调试的诡异 bug. 因为用户时常不知道“父请求”的某个 Nginx 变量的值,其实已经在它的某个“子请求”中被意外修改了。诸如此类的因共享而导致的不好的“副作用”,让包括 ngx_echongx_lua,以及 ngx_srcache 在内的许多第三方模块都选择了禁用父子请求间的变量共享。

时间: 2024-07-29 10:16:34

Nginx 变量漫谈(五)的相关文章

Nginx 变量漫谈(一)

Nginx 的配置文件使用的就是一门微型的编程语言,许多真实世界里的 Nginx 配置文件其实就是一个一个的小程序.当然,是不是“图灵完全的”暂且不论,至少据我观察,它在设计上受 Perl 和 Bourne Shell 这两种语言的影响很大.在这一点上,相比 Apache 和 Lighttpd 等其他 Web 服务器的配置记法,不能不说算是 Nginx 的一大特色了.既然是编程语言,一般也就少不了“变量”这种东西(当然,Haskell 这样奇怪的函数式语言除外了). 熟悉 Perl.Bourne

Nginx 变量漫谈(七)

在 (一) 中我们提到过,Nginx 变量的值只有一种类型,那就是字符串,但是变量也有可能压根就不存在有意义的值.没有值的变量也有两种特殊的值:一种是“不合法”(invalid),另一种是“没找到”(not found). 举例说来,当 Nginx 用户变量 $foo 创建了却未被赋值时,$foo 的值便是“不合法”:而如果当前请求的 URL 参数串中并没有提及 XXX 这个参数,则 $arg_XXX 内建变量的值便是“没找到”. 无论是“不合法”也好,还是“没找到”也罢,这两种 Nginx 变

Nginx 变量漫谈(二)

关于 Nginx 变量的另一个常见误区是认为变量容器的生命期,是与 location 配置块绑定的.其实不然.我们来看一个涉及“内部跳转”的例子:     server {        listen 8080;         location /foo {            set $a hello;            echo_exec /bar;        }         location /bar {            echo "a = [$a]";  

Nginx 变量漫谈(六)

Nginx 内建变量用在“子请求”的上下文中时,其行为也会变得有些微妙. 前面在 (三) 中我们已经知道,许多内建变量都不是简单的“存放值的容器”,它们一般会通过注册“存取处理程序”来表现得与众不同,而它们即使有存放值的容器,也只是用于缓存“存取处理程序”的计算结果.我们之前讨论过的 $args 变量正是通过它的“取处理程序”来返回当前请求的 URL 参数串.因为当前请求也可以是“子请求”,所以在“子请求”中读取 $args,其“取处理程序”会很自然地返回当前“子请求”的参数串.我们来看这样的一

nginx变量(日志)

nginx变量(日志) HTTP请求变量 - arg_PARAMETER.http_HEADER.sent_http_HEADER 它是指http请求中的变量,举例: 修改/etc/nginx/nginx.conf 访问页面 内置变量 - ngnix内置的 http://nginx.org/en/docs/http/ngx_http_log_module.html#log_format

nginx变量

$arg_PARAMETER                 功能:如果在请求中设置了查询字符串,那么这个变量包含在查询字符串是GET请求PARAMETER中的值. $args                                        功能:该变量的值是GET请求在请求行中的参数. $binary_remote_addr          功能:二进制格式的客户端地址 $body_bytes_sent                 功能:响应体的大小,即使发生了中断或者是放

10.C#匿名函数的变量捕获(五章5.5)

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 首先感谢园友的指定,后续的文章一定会多码多想,出来的文章才有说服力.那今天接上篇我们来聊一聊匿名函数,对于匿名函数,我们知道使用delegate关键字,那我们来需要知道匿名函数在变量是的处理方式,先说两个术语,外部变量和捕获的外部变量,可以看出

nginx学习笔记五(nginx的事件模块定义)

在linux后台服务器开发领域里面,epoll的大名是早有所闻.<深入理解nginx>一书在第9章-事件模块中就详细说明了epoll相关的系统调用是怎么嵌入到nginx的框架中. 下面说明nginx框架下与事件处理相关的一些模块. 一.ngx_events_module ngx_events_module是核心模块中的一种.之前一直不是很明白核心模块的意思,现在想来,事件模块的核心模块应该是第一个启动的与事件相关的模块.这个模块并不会去处理实际的事件业务,而是会去做一些基本的初始化操作.ngx

nginx菜鸟教程五

nginx定时任务完成日志切割 date -d yesterday date -s '2014-11-27 08:55:00' clock -w date -d yesterday +%Y%m%d --日志脚本 #!/bin/bash base_path='/usr/local/nginx/logs' log_path=$(date -d yesterday +"%Y%m") day=$(date -d yesterday +"%d") mkdir -p $base