执行过程图

Nginx 的11个执行阶段以及对应的http模块:
image-1669037999812
Nginx 的11个执行阶段的枚举类可以参考nginx源码中的ngx_http_core_module.h 中ngx_http_phases枚举:
nginx-执行流程
11个执行阶段的各个模块的执行流程图:
image-1669037713773

post-read

Nginx的第一个阶段post_read 阶段是在正式处理请求之前工作的。在这个阶段刚刚获取了请求头的信息,还没有进行任何处理。我们可以拿到当前请求的原始数据,比如当前请求的真实IP。
在http协议中有两种方式获取用户IP:

  • X-Forwardex-For:用来传递IP,这个头部会把经过的节点IP都记录下来。
  • X-Rral-IP:可以记录用户真实的IP地址,只能有一个。

ngx_http_realip_module模块

post_read涉及到的模块 ngx_http_realip_module 当前模块不会自动编译进Nginx中所以需要手动编译进入nginx源码文件夹中执行./configure --with-http_realip_module

内嵌变量

  • $realip_remote_addr #保留原始客户地址
  • $realip_remote_port #保留原始客户端端口

模块指令

set_real_ip_from:指定可信的地址,只有从该地址建立的连接,获取的realip才是可信的

句法:set_real_ip_from address | CIDR | unix:;
默认:-
内容:http,server,location

real_ip_header:指定从那个头部获取真实的IP地址,默认从X-Real-IP中取,如果设置从X-Forwarded-For中取,会先从最后一个IP开始取。

句法:real_ip_header field | X-Real-IP | X-Forwarded-For | proxy_protocol;
默认:real_ip_header X-Real-IP;
内容:http,server,location

real_ip_recursive:环回地址,默认关闭,打开的时候,如果X-Forwarded-For最后一个地址与客户端地址相同,会过滤调该地址。

句法:real_ip_recursive on | off;
默认:real_ip_recursive off;
内容:http,server,location

配置示例

set_real_ip_from 192.168.1.0/24;
set_real_ip_from 192.168.2.1;
set_real_ip_from 2001:0db8 :: / 32;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

修改nginx.conf配置文件内容如下:

server {
        listen       80;
        server_name  abbila.com;
        set_real_ip_from 10.211.55.2;
        real_ip_recursive  off;
        #real_ip_recursive  on;
        real_ip_header  X-Forwarded-For;
 
        location / {
                return 200 "client real Ip is:$remote_addr  \n";
        }
}           

上面的配置中设置了可信IP为10.211.55.2,real_ip_recursive是关闭状态的,real_ip_header从X-Forwarded-For获取。测试结果如下:

curl -H "X-Forwarded-For: 1.1.1.1,22.22.22.22,10.211.55.2" abbila.com
client real Ip is:10.211.55.2

然后把real_ip_recursive打开,内容如下:

server {
        listen       80;
        server_name  abbila.com;
        set_real_ip_from 10.211.55.2;
        #real_ip_recursive  off;
        real_ip_recursive  on;
        real_ip_header  X-Forwarded-For;
 
        location / {
                return 200 "client real Ip is:$remote_addr  \n";
        }
}

测试结果如下:

curl -H "X-Forwarded-For: 1.1.1.1,22.22.22.22,10.211.55.2" abbila.com
client real Ip is:22.22.22.22

可以通过上面的测试看出,如果real_ip_recursive关闭的话,获取的realIp为X-Forwarded-For的最后一个IP如果是打开状态的话那就把X-Forwarded-For中与可信IP重复的IP过滤掉然后取剩下的最后一个IP为realip。如果使用 X-Forwarded-For 获取 realip 的话,real_ip_recursive 需要打开。并且,realip 依赖于 set_real_ip_from 设置的可信地址。那么有人可能就会问了,那直接用 X-Real-IP 来选取真实的 IP 地址不就好了。这是可以的,但是 X-Real-IP 是 Nginx 独有的,如果客户端与服务器之间还有其他非 Nginx 软件实现的代理,就会造成取不到 X-Real-IP 头部,所以这个要根据实际情况来定。

server-rewrite

阶段标准函数:

  • ngx_rewrite
  • set
  • openresty函数set_by_lua、rewrite_by_lua

post-read阶段之后便是server-rewrite阶段。当ngx_rewrite模块的配置指令直接书写在server配置块中时,基本上都是运行在server-rewrite阶段。

server {
    listen 8080;
    location /test {
        set $b "$a, world";
        echo $b;
    }
    set $a hello;
}

这里,配置语句set a hello 直接卸载了server配置块中,因此它就运行在server-rewrite阶段。而server-rewrite阶段要早于rewrite阶段运行,因此卸载location配置块中的语句 set b "a,world"便晚于外面的set a hello 语句运行。

$ curl localhost:8080/test
hello, world

find-config

这个阶段并不支持Nginx模块注册处理程序,而是由Nginx核心来完成当前请求与location配置块之间的配对工作。

location /hello {
    echo "hello world";
}

rewrite

阶段标准函数:

  • set_unescape_uri
  • rewrite
  • openresty函数set_by_lua、rewrite_by_lua

post-rewrite

post-rewrite阶段,不接受Nginx模块注册处理程序,而是由Nginx核心完成rewrite阶段所要求的内部跳转操作
内部跳转工作原理:本质上其实就是把当前的请求处理阶段强行倒退到find-config阶段,以便重新进行请求URI与location配置块的配对。比如下例中,运行在rewrite阶段的rewrite指令就让当前请求的处理阶段倒退回了find-config阶段。由于此时当前请求的URI已经被rewrite指令修改为了/bar,所以这一次换成了location /bar 与当前请求相关联,然后再接着从rewrite阶段往下执行。为什么不直接在rewrite指令执行时立即进行跳转呢?是为了在最初匹配的location块中支持多次反复地改写URI

    server {
        listen 8080;
        location /foo {
            set $a hello;
            rewrite ^ /bar;
        }
        location /bar {
            echo "a = [$a]";
        }
    }
    location /foo {
        rewrite ^ /bar;
        rewrite ^ /baz;
        echo foo;
    }
    location /bar {
        echo bar;
    }
    location /baz {
        echo baz;
    }

注意:如果在server配置块中直接使用rewrite配置指令对请求URI进行改写,则不会涉及”内部跳转“

server {
    listen 8080;
    rewrite ^/foo /bar;
    location /foo {
        echo foo;
    }
    location /bar {
        echo bar;
    }
}

preaccess

阶段标准函数:

  • ngx_access_allow
  • ngx_access_deny
  • ngx_limit_zone
  • ngx_auth_request
server {
    listen 8080;
 
    location /test {
        set_real_ip_from 127.0.0.1;
        real_ip_header X-Real-IP;
 
        echo "from: $remote_addr";
    }
}

与之前的例子相比,此例最重要的区别在于把ngx_realip的配置指令放在了location配置块中。前面我们介绍过,Nginx匹配location的动作发生在find-config阶段,而find-config阶段远远晚于post-read阶段执行,所以在post-read阶段,当前请求还没有和任何location相关联。
建议尽量在server配置块中配置ngx_realip这样的模块。

access

模块:

  • ngx_http_access_moudule
  • ngx_heep_auth_basic_module

post-access

该阶段不支持Nginx模块注册处理程序,而是由Nginx核心自己完成一些处理工作。

try-files

实现标准配置指令tra_files的功能,并不支持Nginx模块注册处理程序。
try_files指令接受两个以上任意数量的参数,每个参数都指定了一个URI,这里假设配置了N个参数,则Nginx会在try-files阶段,依次把前N-1个参数映射为文件系统上的对象(文件或目录),然后检查这些对象是否存在。一旦Nginx发现某个文件系统对象存在,就会在try-files阶段把当前请求的URI改写为该对象所对应的参数URI(但不会包含末尾的斜杠支付,也不会发生”内部跳转“)。如果前N-1个参数所对应的文件系统对象都不存在,try-files阶段就会立即发起”内部跳转“到最后一个参数(即第N个参数)所指定的URI。

location /test {
    try_files /foo /bar/ /baz;
    echo "uri: $uri";
}
 
location /foo {
    echo foo;
}
 
location /bar/ {
    echo bar;
}
 
location /baz {
    echo baz;
}

在location /test 中使用了try_files指令,提供了3个参数,/foo、/bar/和/baz。它会在try-files阶段依次检查前面两个参数/foo和/bar/所对应的文件系统对象是否存在。
假设现在/var/www/路径下是空的,则第一个参数/foo映射成的文件/var/www/foo是不存在的,同样,对于第二个参数/bar/所映射成的目录/var/www/bar/也是不存在的。于是此时Nginx会在try-files阶段发起最后一个参数所指定的URI(即/baz)的“内部跳转”。请求及返回如下:

 $ curl localhost:8080/test
   baz

在/var/www/下创建一个名为foo的文件,其内容为hello world,然后请求/test接口:

 $ curl localhost:8080/test
  uri: /foo

try_files指令的第一个参数/foo可以映射为文件/var/www/foo,而Nginx在try-files阶段发现此文件确实存在,于是立即把当前请求的URI改写为这个参数的值,及/foo,并且不再继续检查后面的参数,而是直接运行后面的请求处理阶段。
通过上面的示例可以看出,try_files指令本质上只是有条件的改写当前请求的URI,而这里说的“条件”其实就是文件系统上的对象是否存在。当”条件“都不满足时,他就会无条件的发起一个指定的”内部跳转“。除了发起”内部跳转“外,try_files指令还支持直接返回指定状态码的HTTP错误页,例如:

 try_files /foo /bar/ =404;

当/foo和/bar/参数对应的文件系统对象都不存在时,就直接返回404 Not Found错误页。

content

阶段标准函数:

  • echo
  • proxy_pass

所有请求的标准输出都在该阶段。几乎所有逻辑代码也在该阶段执行。这个阶段比较常见

log

阶段标准函数:

  • access_log
  • error_log

星霜荏苒 居诸不息