Nginx 配置指令的执行顺序(一)
大多数 Nginx 新手都会频繁遇到这样一个困惑,那就是当同一个 location
配置块使用了多个 Nginx 模块的配置指令时,这些指令的执行顺序很可能会跟它们的书写顺序大相径庭。于是许多人选择了“试错法”,然后他们的配置文件就时常被改得一片狼藉。这个系列的教程就旨在帮助读者逐步地理解这些配置指令背后的执行时间和先后顺序的奥秘。
现在就来看这样一个令人困惑的例子:
从这个例子的本意来看,我们期望的输出是一行 32
和一行 56
,因为我们第一次用 echo 配置指令输出了 $a
变量的值以后,又紧接着使用 set 配置指令修改了 $a
. 然而不幸的是,事实并非如此:
$ curl 'http://localhost:8080/test
56
56
我们看到,语句 set $a 56
似乎在第一条 echo $a
语句之前就执行过了。这究竟是为什么呢?难道我们遇到了 Nginx 中的一个 bug?
显然,这里并没有 Nginx 的 bug;要理解这里发生的事情,就首先需要知道 Nginx 处理每一个用户请求时,都是按照若干个不同阶段(phase)依次处理的。
Nginx 的请求处理阶段共有 11 个之多,我们先介绍其中 3 个比较常见的。按照它们执行时的先后顺序,依次是 rewrite
阶段、access
阶段以及 content
阶段(后面我们还有机会见到其他更多的处理阶段)。
所有 Nginx 模块提供的配置指令一般只会注册并运行在其中的某一个处理阶段。比如上例中的 set 指令就是在 rewrite
阶段运行的,而 echo 指令就只会在 content
阶段运行。前面我们已经知道,在单个请求的处理过程中,rewrite
阶段总是在 content
阶段之前执行,因此属于 rewrite
阶段的配置指令也总是会无条件地在content
阶段的配置指令之前执行。于是在同一个 location
配置块中,set 指令总是会在 echo 指令之前执行,即使我们在配置文件中有意把 set 语句写在 echo 语句的后面。
回到刚才那个例子,
实际的执行顺序应当是
即先在 rewrite
阶段执行完这里的两条 set 赋值语句,然后再在后面的 content
阶段依次执行那两条 echo语句。分属两个不同处理阶段的配置指令之间是不能穿插着运行的。
为了进一步验证这一点,我们不妨借助 Nginx 的“调试日志”来一窥 Nginx 的实际执行过程。
因为这是我们第一次提及 Nginx 的“调试日志”,所以有必要先简单介绍一下它的启用方法。调试日志默认是禁用的,因为它会引入比较大的运行时开销,让 Nginx 服务器显著变慢。一般我们需要重新编译和构造 Nginx 可执行文件,并且在调用 Nginx 源码包提供的 ./configure
脚本时传入 --with-debug
命令行选项。例如我们下载完 Nginx 源码包后在 Linux 或者 Mac OS X 等系统上构建时,典型的步骤是这样的:
tar xvf nginx-1.0.10.tar.gz
cd nginx-1.0.10/
./configure --with-debug
make
sudu make install
如果你使用的是我维护的 ngx_openresty 软件包,则同样可以向它的 ./configure
脚本传递 --with-debug
命令行选项。
当我们启用 --with-debug
选项重新构建好调试版的 Nginx 之后,还需要同时在配置文件中通过标准的error_log 配置指令为错误日志使用 debug
日志级别(这同时也是最低的日志级别):
error_log logs/error.log debug;
这里重要的是 error_log 指令的第二个参数,debug
,而前面第一个参数是错误日志文件的路径,logs/error.log
. 当然,你也可以指定其他路径,但后面我们会检查这个文件的内容,所以请特别留意一下这里实际配置的文件路径。
现在我们重新启动 Nginx(注意,如果 Nginx 可执行文件也被更新过,仅仅让 Nginx 重新加载配置是不够的,需要关闭再启动 Nginx 主服务进程),然后再请求一下我们刚才那个示例接口:
$ curl 'http://localhost:8080/test'
56
56
现在可以检查一下前面配置的 Nginx 错误日志文件中的输出。因为文件中的输出比较多(在我的机器上有 700 多行),所以不妨用 grep
命令在终端上过滤出我们感兴趣的部分:
grep -E 'http (output filter|script (set|value))' logs/error.log
在我机器上的输出是这个样子的(为了方便呈现,这里对 grep
命令的实际输出作了一些简单的编辑,略去了每一行的行首时间戳):
[debug] 5363#0: *1 http script value: "32"
[debug] 5363#0: *1 http script set $a
[debug] 5363#0: *1 http script value: "56"
[debug] 5363#0: *1 http script set $a
[debug] 5363#0: *1 http output filter "/test?"
[debug] 5363#0: *1 http output filter "/test?"
[debug] 5363#0: *1 http output filter "/test?"
这里需要稍微解释一下这些调试信息的具体含义。set 配置指令在实际运行时会打印出两行以 http script
起始的调试信息,其中第一行信息是 set 语句中被赋予的值,而第二行则是 set 语句中被赋值的 Nginx 变量名。于是上面首先过滤出来的
[debug] 5363#0: *1 http script value: "32"
[debug] 5363#0: *1 http script set $a
这两行就对应我们例子中的配置语句
set $a 32;
而接下来这两行调试信息
[debug] 5363#0: *1 http script value: "56"
[debug] 5363#0: *1 http script set $a
则对应配置语句
set $a 56;
此外,凡在 Nginx 中输出响应体数据时,都会调用 Nginx 的所谓“输出过滤器”(output filter),我们一直在使用的 echo 指令自然也不例外。而一旦调用 Nginx 的“输出过滤器”,便会产生类似下面这样的调试信息:
[debug] 5363#0: *1 http output filter "/test?"
当然,这里的 "/test?"
部分对于其他接口可能会发生变化,因为它显示的是当前请求的 URI. 这样联系起来看,就不难发现,上例中的那两条 set 语句确实都是在那两条 echo 语句之前执行的。
细心的读者可能会问,为什么这个例子明明只使用了两条 echo 语句进行输出,但却有三行 http output filter
调试信息呢?其实,前两行 http output filter
信息确实分别对应那两条 echo 语句,而最后那一行信息则是对应 ngx_echo 模块输出指示响应体末尾的结束标记。正是为了输出这个特殊的结束标记,才会多出一次对 Nginx “输出过滤器”的调用。包括 ngx_proxy 在内的许多模块在输出响应体数据流时都具有此种行为。
现在我们就不会再为前面那个例子输出两行一模一样的 56
而感到惊讶了。我们根本没有机会在第二条 set语句之前用 echo 输出。幸运的是,仍然可以借助一些小技巧来达到最初的目的:
此时的输出便符合那个问题示例的初衷了:
$ curl 'http://localhost:8080/test'
32
56
这里通过引入新的用户变量 $saved_a
,在改写 $a
之前及时保存了 $a
的初始值。而对于多条 set 指令而言,它们之间的执行顺序是由 ngx_rewrite 模块来保证与书写顺序相一致的。同理,ngx_echo 模块自身也会保证它的多条 echo 指令之间的执行顺序。
细心的读者应当发现,我们在 Nginx 变量漫谈系列 的示例中已经广泛使用了这种技巧,来绕过因处理阶段而引起的指令执行顺序上的限制。
看到这里,有的读者可能会问:“那么我在使用一条陌生的配置指令之前,如何知道它究竟运行在哪一个处理阶段呢?”答案是:查看该指令的文档(当然,高级开发人员也可以直接查看模块的 C 源码)。在许多模块的文档中,都会专门标记其配置指令所运行的具体阶段。例如 echo 指令的文档中有这么一行:
phase: content
这一行便是说,当前配置指令运行在 content
阶段。如果你使用的 Nginx 模块碰巧没有指示运行阶段的文档,可以直接联系该模块的作者请求补充。不过,值得一提的是,并非所有的配置指令都与某个处理阶段相关联,例如我们先前在 Nginx 变量漫谈(一) 中提到过的 geo 指令以及在 Nginx 变量漫谈(四) 中介绍过的 map 指令。这些不与处理阶段相关联的配置指令基本上都是“声明性的”(declarative),即不直接产生某种动作或者过程。Nginx 的作者 Igor Sysoev 在公开场合曾不止一次地强调,Nginx 配置文件所使用的语言本质上是“声明性的”,而非“过程性的”(procedural)。
Nginx 配置指令的执行顺序(一)的更多相关文章
- Nginx 配置指令的执行顺序(八)
前面我们详细讨论了 rewrite.access 和 content 这三个最为常见的 Nginx 请求处理阶段,在此过程中,也顺便介绍了运行在这三个阶段的众多 Nginx 模块及其配置指令.同时可以 ...
- Nginx 配置指令的执行顺序(五)
Nginx 的 content 阶段是所有请求处理阶段中最为重要的一个,因为运行在这个阶段的配置指令一般都肩负着生成“内容”(content)并输出 HTTP 响应的使命.正因为其重要性,这个阶段的配 ...
- Nginx配置指令的执行顺序
rewrite阶段 rewrite阶段是一个比较早的请求处理阶段,这个阶段的配置指令一般用来对当前请求进行各种修改(比如对URI和URL参数进行改写),或者创建并初始化一系列后续处理阶段可能需要的Ng ...
- Nginx 配置指令的执行顺序(十)
运行在 post-rewrite 阶段之后的是所谓的 preaccess 阶段.该阶段在 access 阶段之前执行,故名preaccess. 标准模块 ngx_limit_req 和 ngx_lim ...
- Nginx 配置指令的执行顺序(六)
前面我们在 (五) 中提到,在一个 location 中使用 content 阶段指令时,通常情况下就是对应的 Nginx 模块注册该 location 中的“内容处理程序”.那么当一个 locati ...
- Nginx 配置指令的执行顺序(三)
如前文所述,除非像 ngx_set_misc 模块那样使用特殊技术,其他模块的配置指令即使是在 rewrite 阶段运行,也不能和 ngx_rewrite 模块的指令混合使用.不妨来看几个这样的例子. ...
- Nginx 配置指令的执行顺序(二)
我们前面已经知道,当 set 指令用在 location 配置块中时,都是在当前请求的 rewrite 阶段运行的.事实上,在此上下文中,ngx_rewrite 模块中的几乎全部指令,都运行在 rew ...
- Nginx 配置指令的执行顺序
在一个 location 中使用 content 阶段指令时,通常情况下就是对应的 Nginx 模块注册该 location 中的“内容处理程序”.那么当一个 location 中未使用任何 cont ...
- Nginx 配置指令的执行顺序(十一)
紧跟在 post-access 阶段之后的是 try-files 阶段.这个阶段专门用于实现标准配置指令 try_files 的功能,并不支持 Nginx 模块注册处理程序.由于 try_files ...
随机推荐
- python笔记之常用模块用法分析
python笔记之常用模块用法分析 内置模块(不用import就可以直接使用) 常用内置函数 help(obj) 在线帮助, obj可是任何类型 callable(obj) 查看一个obj是不是可以像 ...
- 清空DNS缓存
昨天写了个Python的脚本,功能就是爬取一个网页上的Google ip地址再写入到本机的hosts里面去. 但是写完并且运行完成之后发现上不了Google.于是想到了是不是要清空一下DNS的缓存.不 ...
- oc语言--语法
一.OC简介 1.简介 它是C语言的基础上,增加了一层面向对象语法 OC完全兼容C语言 可以在OC代码中混入C语言代码,甚至是C++代码 可以使用OC开发mac OS X平台和IOS平台的应用程序 2 ...
- LigerUI 分页 MVC
Javascirpt代码 $("#showData").ligerGrid({ columns: [ { display: '编号', name: 'L_ID', align: ' ...
- POJ 2533 Longest Ordered Subsequence - from lanshui_Yang
题目大意:求一个数列的最长上升子序列(严格上升). 解题思路: 方法一:O(n^2) dp[i]:表示处理到第i个位置,序列的最长上升子序列末尾为i的长度: a[]数组存储原序列 dp[i] = ma ...
- perl 爬取数据<1>
use LWP::UserAgent; use POSIX; use DBI; $user="root"; $passwd="11111111"; $dbh=& ...
- 【转】Android 平台下使用 i2c-tools
原文网址:http://my.oschina.net/luoly/blog/368881 Android 平台下使用 i2c-tools Andorid 开发板为 Freescale imx6 的 S ...
- poj 1001 求高精度幂
本题的测试用例十分刁钻,必须要考虑到很多的细节问题,在这里给出一组测试用例及运行结果: 95.123 12 548815620517731830194541.899025343415715973535 ...
- LR实战之Discuz开源论坛——登录场景设计
以下是根据个人项目经验,对登录场景的设计,如下步骤: 一.打开Controller,添加登录脚本,选择“手动场景”,一般我们项目中经常使用的是“手动场景”类型设计,如图 二.在“设计”部分,设置场景的 ...
- 虚拟化之KVM virsh常用命令篇
1,查看运行的虚拟机 virsh list 2,查看所有的虚拟机(关闭和运行的虚拟机) virsh list --all 3,连接虚拟机 virsh console +域名(虚拟机的名称) 4,退出虚 ...