Nginx 配置指令的执行顺序(一)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 大多数 Nginx 新手都会频繁遇到这样一个困惑,那就是当同一个 location 配置块使用了多个 Nginx 模块的配置指令时,这些指令的执行顺序很可能会跟它们的书写顺序大相径庭。于是许多人选择了“试错法”,然后他们的配置文件就时常被改得一片狼藉。

大多数 Nginx 新手都会频繁遇到这样一个困惑,那就是当同一个 location 配置块使用了多个 Nginx 模块的配置指令时,这些指令的执行顺序很可能会跟它们的书写顺序大相径庭。于是许多人选择了“试错法”,然后他们的配置文件就时常被改得一片狼藉。这个系列的教程就旨在帮助读者逐步地理解这些配置指令背后的执行时间和先后顺序的奥秘。

 

    现在就来看这样一个令人困惑的例子:

    ? location /test {
    ?     set $a 32;
    ?     echo $a;
    ?
    ?     set $a 56;
    ?     echo $a;
    ? }

从这个例子的本意来看,我们期望的输出是一行 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 语句的后面。

 

    回到刚才那个例子,

    set $a 32;
    echo $a;
 
    set $a 56;
    echo $a;

实际的执行顺序应当是

    set $a 32;
    set $a 56;
    echo $a;
    echo $a;

即先在 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 输出。幸运的是,仍然可以借助一些小技巧来达到最初的目的:

    location /test {
        set $a 32;
        set $saved_a $a;
        set $a 56;
 
        echo $saved_a;
        echo $a;
    }

此时的输出便符合那个问题示例的初衷了:

    $ 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)。

相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
目录
相关文章
|
1月前
|
编解码 应用服务中间件 Linux
centos配置nginx-rtmp实现ffmpeg转码rtsp为rtmp视频流
centos配置nginx-rtmp实现ffmpeg转码rtsp为rtmp视频流
165 1
|
5月前
|
应用服务中间件 Linux 网络安全
Centos 8.0中Nginx配置文件和https正书添加配置
这是一份Nginx配置文件,包含HTTP与HTTPS服务设置。主要功能如下:1) 将HTTP(80端口)请求重定向至HTTPS(443端口),增强安全性;2) 配置SSL证书,支持TLSv1.1至TLSv1.3协议;3) 使用uWSGI与后端应用通信(如Django);4) 静态文件托管路径设为`/root/code/static/`;5) 定制错误页面(404、50x)。适用于Web应用部署场景。
655 87
|
5月前
|
负载均衡 应用服务中间件 nginx
Nginx配置与命令
Nginx 是一款高性能的 HTTP 和反向代理服务器,其配置文件灵活且功能强大。本文介绍了 Nginx 配置的基础结构和常用指令,包括全局块、Events 块、HTTP 块及 Server 块的配置方法,以及静态资源服务、反向代理、负载均衡、HTTPS 和 URL 重写等功能实现。此外,还提供了常用的 Nginx 命令操作,如启动、停止、重载配置和日志管理等,帮助用户高效管理和优化服务器性能。
|
27天前
|
Ubuntu 安全 应用服务中间件
详细指南:配置Nginx服务器在Ubuntu平台上
以上步骤涵盖了基本流程:从软件包管理器获取 Ngnix, 设置系统服务, 调整UFW规则, 创建并激活服务器块(也称作虚拟主机), 并进行了初步优化与加固措施。这些操作都是建立在命令行界面上,并假设用户具有必要权限(通常是root用户)来执行这些命令。每个操作都有其特定原因:例如,设置开机启动确保了即使重启后也能自动运行 Ngnix;而编辑server block则定义了如何处理进入特定域名请求等等。
189 18
|
29天前
|
Ubuntu 安全 应用服务中间件
详细指南:配置Nginx服务器在Ubuntu平台上
以上步骤涵盖了基本流程:从软件包管理器获取 Ngnix, 设置系统服务, 调整UFW规则, 创建并激活服务器块(也称作虚拟主机), 并进行了初步优化与加固措施。这些操作都是建立在命令行界面上,并假设用户具有必要权限(通常是root用户)来执行这些命令。每个操作都有其特定原因:例如,设置开机启动确保了即使重启后也能自动运行 Ngnix;而编辑server block则定义了如何处理进入特定域名请求等等。
196 17
|
2月前
|
数据建模 应用服务中间件 PHP
配置nginx容器和php容器协同工作成功,使用ip加端口的方式进行通信
本示例演示如何通过Docker挂载同一宿主目录至Nginx与PHP容器,实现PHP项目运行环境配置。需注意PHP容器中监听地址修改为0.0.0.0:9000,并调整Nginx配置中fastcgi_pass指向正确的IP与端口。同时确保Nginx容器中/var/www/html权限正确,以避免访问问题。
配置nginx容器和php容器协同工作成功,使用ip加端口的方式进行通信
|
3月前
|
应用服务中间件 网络安全 nginx
配置Nginx以支持Websocket连接的方法。
通过上述配置,Nginx将能够理解WebSocket协议的特殊要求,代理Websocket流量到合适的后端服务器。注意,Websocket并不是HTTP,尽管它最初是通过HTTP请求启动的连接升级,因此保证Nginx了解并能够妥善处理这种升级流程是关键。
668 10
|
2月前
|
Ubuntu 应用服务中间件 Linux
在Ubuntu上配置Nginx实现开机自启功能
至此,Nginx应该已经被正确地设置为开机自启。在Ubuntu中利用 `systemd`对服务进行管理是一种高效的方式,为系统管理员提供了强大的服务管理能力,包括但不限于启动、停止、重启服务,以及配置服务的开机自启动。通过这些简洁的命令,即使是对Linux不太熟悉的用户也能轻松地进行配置。
126 0
|
4月前
|
安全 应用服务中间件 网络安全
Nginx SSL/TLS协议栈中配置深度解析与实践指南-优雅草卓伊凡
Nginx SSL/TLS协议栈中配置深度解析与实践指南-优雅草卓伊凡
272 0
Nginx SSL/TLS协议栈中配置深度解析与实践指南-优雅草卓伊凡
|
4月前
|
JSON 前端开发 应用服务中间件
配置Nginx根据IP地址进行流量限制以及返回JSON格式数据的方案
最后,记得在任何生产环境部署之前,进行透彻测试以确保一切运转如预期。遵循这些战术,守卫你的网络城堡不再是难题。
208 3