NginxDirectiveExecOrderTutorialCn08

= Nginx 配置指令的执行顺序（八） =

前面我们详细讨论了 、  和   这三个最为常见的 Nginx 请求处理阶段，在此过程中，也顺便介绍了运行在这三个阶段的众多 Nginx 模块及其配置指令. 同时可以看到，请求处理阶段的划分直接影响到了配置指令的执行顺序，熟悉这些阶段对于正确配置不同的 Nginx 模块并实现它们彼此之间的协同工作是非常必要的. 所以接下来我们接着讨论余下的那些阶段.

前面在 （一） 中提到，Nginx 处理请求的过程一共划分为 11 个阶段，按照执行顺序依次是 、 、 、 、 、 、 、 、 、  以及.

最先执行的  阶段在 Nginx 读取并解析完请求头（request headers）之后就立即开始运行. 这个阶段像前面介绍过的  阶段那样支持 Nginx 模块注册处理程序. 比如标准模块 ngx_realip 就在  阶段注册了处理程序，它的功能是迫使 Nginx 认为当前请求的来源地址是指定的某一个请求头的值. 下面这个例子就使用了 ngx_realip 模块提供的 set_real_ip_from 和 real_ip_header 这两条配置指令：

server { listen 8080;

set_real_ip_from 127.0.0.1; real_ip_header  X-My-IP;

location /test { set $addr $remote_addr; echo "from: $addr"; }   }

这里的配置是让 Nginx 把那些来自  的所有请求的来源地址，都改写为请求头   所指定的值. 同时该例使用了标准内建变量 $remote_addr 来输出当前请求的来源地址，以确认是否被成功改写.

首先在本地请求一下这个  接口：

$ curl -H 'X-My-IP: 1.2.3.4' localhost:8080/test from: 1.2.3.4

这里使用了 curl 工具的  选项指定了额外的 HTTP 请求头. 从输出可以看到，$remote_addr 变量的值确实在  阶段就已经成为了   请求头中指定的值，即. 那么 Nginx 究竟是在什么时候改写了当前请求的来源地址呢？答案是：在  阶段. 由于  阶段的运行远在   阶段之后，所以当在   配置块中通过 set 配置指令读取 $remote_addr 内建变量时，读出的来源地址已经是经过   阶段篡改过的.

如果在请求上例中的  接口时没有指定   请求头，或者提供的   请求头的值不是合法的 IP 地址，那么 Nginx 就不会对来源地址进行改写，例如：

$ curl localhost:8080/test from: 127.0.0.1

$ curl -H 'X-My-IP: abc' localhost:8080/test from: 127.0.0.1

如果从另一台机器访问这个  接口，那么即使指定了合法的   请求头，也不会触发 Nginx 对来源地址进行改写. 这是因为上例已经使用 set_real_ip_from 指令规定了来源地址的改写操作只对那些来自  的请求生效. 这种过滤机制可以避免来自其他不受信任的地址的恶意欺骗. 当然，也可以通过 set_real_ip_from 指令指定一个 IP 网段（利用 （三） 中介绍过的“CIDR 记法”）. 此外，同时配置多个 set_real_ip_from 语句也是允许的，这样可以指定多个受信任的来源地址或地址段. 下面是一个例子：

set_real_ip_from 10.32.10.5; set_real_ip_from 127.0.0.0/24;

有的读者可能会问，ngx_realip 模块究竟有什么实际用途呢？为什么我们需要去改写请求的来源地址呢？答案是：当 Nginx 处理的请求经过了某个 HTTP 代理服务器的转发时，这个模块就变得特别有用. 当原始的用户请求经过转发之后，Nginx 接收到的请求的来源地址无一例外地变成了该代理服务器的 IP 地址，于是 Nginx 以及 Nginx 背后的应用就无法知道原始请求的真实来源. 所以，一般我们会在 Nginx 之前的代理服务器中把请求的原始来源地址编码进某个特殊的 HTTP 请求头中（例如上例中的  请求头），然后再在 Nginx 一侧把这个请求头中编码的地址恢复出来. 这样 Nginx 中的后续处理阶段（包括 Nginx 背后的各种后端应用）就会认为这些请求直接来自那些原始的地址，代理服务器就仿佛不存在一样. 正是因为这个需求，所以 ngx_realip 模块才需要在第一个处理阶段，即  阶段，注册处理程序，以便尽可能早地改写请求的来源.

阶段之后便是  阶段. 我们曾在 （二） 中简单提到，当 ngx_rewrite 模块的配置指令直接书写在  配置块中时，基本上都是运行在   阶段. 下面就来看这样的一个例子：

server { listen 8080;

location /test { set $b "$a, world"; echo $b; }

set $a hello; }

这里，配置语句  直接写在了   配置块中，因此它就运行在   阶段. 而  阶段要早于   阶段运行，因此写在   配置块中的语句   便晚于外面的   语句运行. 该例的测试结果证明了这一点：

$ curl localhost:8080/test hello, world

由于  阶段位于   阶段之后，所以   配置块中的 set 指令也就总是运行在 ngx_realip 模块改写请求的来源地址之后. 来看下面这个例子：

server { listen 8080;

set $addr $remote_addr;

set_real_ip_from 127.0.0.1; real_ip_header  X-Real-IP;

location /test { echo "from: $addr"; }   }

请求  接口的结果如下：

$ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test from: 1.2.3.4

在这个例子中，虽然 set 指令写在了 ngx_realip 的配置指令之前，但仍然晚于 ngx_realip 模块执行. 所以  变量在   阶段被 set 指令赋值时，从 $remote_addr 变量读出的来源地址已经是经过改写过的了.