Nginx 处理请求的过程一共划分为 11 个阶段,按照执行顺序依次是 post-read、server-rewrite、find-config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content 以及 log。

nginx处理请求的11个阶段

POST_READ 阶段:

    POST_READ阶段是nginx处理请求流程中第一个可以添加模块函数的阶段,任何需要在接收完请求头之后立刻处理的逻辑可以在该阶段注册处理函数。nginx源码中只有realip模块在该阶段注册了函数,当nginx前端多了一个7层负载均衡层,并且客户端的真实ip被前端保存在请求头中时,该模块用来将客户端的ip替换为请求头中保存的值。realip模块之所以在POST_READ阶段执行的原因是它需要在其他模块执行之前悄悄的将客户端ip替换为真实值,而且它需要的信息仅仅只是请求头。一般很少有模块需要注册在POST_READ阶段,realip模块默认没有编译进nginx。

示例:

server {
  listen 80;
  server_name www.imcati.com;
  set_real_ip_from 172.17.0.0/16;
  real_ip_header x-real-ip;

  location / {
     set $addr $remote_addr;
     return 200 "real-ip: $addr";
   }
}

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

本地curl确认是否进行修改: curl -H "x-real-ip: 114.114.114.114" http://www.imcati.com/

nginx处理请求的11个阶段

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

SERVER_REWRITE 阶段:
    SERVER_REWRITE阶段是nginx中第一个必须经历的重要phase,请求进入此阶段时已经找到对应的虚拟主机(server)配置。nginx的rewrite模块在这个阶段注册了一个handler,rewrite模块提供url重写指令rewrite,变量设置指令set,以及逻辑控制指令ifbreakreturn,用户可以在server配置里面,组合这些指令来满足自己的需求,而不需要另外写一个模块,比如将一些前缀满足特定模式的uri重定向到一个固定的url,还可以根据请求的属性来决定是否需要重写或者给用户发送特定的返回码。rewrite提供的逻辑控制指令能够满足一些简单的需求,针对一些较复杂的逻辑可能需要注册handler通过独立实现模块的方式来满足。
    需要注意该阶段和后面的REWRITE阶段的区别,在SERVER_REWRITE阶段中,请求还未被匹配到一个具体的location中。该阶段执行的结果(比如改写后的uri)会影响后面FIND_CONFIG阶段的执行。另外这个阶段也是内部子请求执行的第一个阶段。

示例:

server {
   listen 80;
   server_name www.imcati.com;
   location /test {
     set $b "$a, world";
     return 200 "args: $b";
   }
     set $a hello;
}

本地curl查看返回:curl http://www.imcati.com/test/

nginx处理请求的11个阶段 

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

FIND_CONFIG 阶段:
    FIND_CONFIG阶段顾名思义就是寻找配置阶段,具体一点就是根据uri查找location配置,实际上就是设置r->loc_conf,在此之前r->loc_conf使用的server级别的,查找location过程由函数ngx_http_core_find_location完成,值得注意的是当ngx_http_core_find_location函数返回NGX_DONE时,Nginx会返回301,将用户请求做一个重定向,这种情况仅发生在该location使用了proxy_pass/fastcgi/scgi/uwsgi/memcached模块,且location的名字以/符号结尾,并且请求的uri为该location除/之外的前缀,比如对location /xx/,如果某个请求/xx访问到该location,则会被重定向为/xx/。另外Nginx中location可以标识为internal,即内部location,这种location只能由子请求或者内部跳转访问。找到location配置后,Nginx调用了ngx_http_update_location_config函数来更新请求相关配置,其中最重要的是更新请求的content handler,不同location可以有自己的content handler。最后,由于有REWRITE_PHASE的存在,FIND_CONFIG阶段可能会被执行多次。

示例:

server {
   listen 80;
   server_name www.imcati.com;
   location / {
     return 200 "hello,world";
   }
}

REWRITE 阶段:
    REWRITE阶段为location级别的重写,这个阶段的checker和SERVER_REWRITE阶段的是同一个函数,而且Nginx的rewrite模块对这2个阶段注册的是同一个handler,2者唯一区别就是执行时机不一样,REWRITE阶段为location级别的重写,SERVER_REWRITE执行之后是FIND_CONFIG阶段,REWRITE阶段执行之后是POST_REWRITE阶段。

示例:

server {
   listen 80;
   server_name www.imcati.com;
   location / {
     root /usr/share/nginx/html;
     rewrite . /rw/page break;
   }
}

POST_REWRITE 阶段:

    该阶段不能注册handler,仅仅只是检查上一阶段是否做了uri重写,如果没有重写的话,直接进入下一阶段;如果有重写的话,则利用next跳转域往前跳转到FIND_CONFIG阶段重新执行。Nginx对uri重写次数做了限制,默认是10次。

PREACCESS 阶段:
    进入该阶段表明Nginx已经将请求确定到了某一个location(当该server没有任何location时,也可能是server),如论如何请求的loc_conf配置已经确定下来,该阶段一般用来做资源控制,默认情况下,诸如ngx_http_limit_conn_module,ngx_http_limit_req_module等模块会在该阶段注册handler,用于控制连接数,请求速率等。PREACCESS阶段使用的checker是默认的ngx_http_core_generic_phase函数。

示例:

limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
   listen 80;
   server_name www.imcati.com;
   location / {
     limit_conn addr 1; //指定允许请求连接数
     limit_rate 1k; //限定网络传输速率
     root /usr/share/nginx/html;
}}

通过jmeter打压测试:

发送1个请求:

nginx处理请求的11个阶段

发送两个请求:

nginx处理请求的11个阶段

503是超过限制默认返回状态码。 

ACCESS 阶段:
    该阶段的首要目的是做权限控制,默认情况下,Nginx的ngx_http_access_module和ngx_http_auth_basic_module模块分别会在该阶段注册一个handler。

    ACCESS阶段的checker是ngx_http_core_access_phase函数,此函数对handler返回值的处理大致和ngx_http_core_generic_phase一致,特殊的地方是当clcf->satisfy为NGX_HTTP_SATISFY_ALL,也就是需要满足该阶段注册的所有handler的验证时,某个handler返回NGX_OK时还需要继续处理本阶段的其他handler。clcf->satisfy的值可以使用satisfy指令指定。

示例:

server {
    listen       80;
    server_name  www.imcati.com;
    
    auth_basic "User Authentication";
    auth_basic_user_file /etc/nginx/.passwd-www;
location / { root /usr/share/nginx/html; index index.html; } }
添加认证文件:
htpasswd -c /etc/nginx/.passwd-www www ;
页面访问:

nginx处理请求的11个阶段

POST_ACCESS 阶段:
    POST_ACCESS和POST_REWRITE阶段一样,只是处理一下上一阶段的结果,而不能挂载自己的handler,具体为如果ACCESS阶段返回了NGX_HTTP_FORBIDDEN或NGX_HTTP_UNAUTHORIZED(记录在r->access_code字段),该阶段会结束掉请求。

TRY_FILES 阶段:
    TRY_FILES阶段仅当配置了try_files指令时生效,实际上该指令不常用,它的功能是指定一个或者多个文件或目录,最后一个参数可以指定为一个location或一个返回码,当设置了该指令时,TRY_FILES阶段调用checker函数ngx_http_core_try_files_phase来依此检查指定的文件或目录是否存在,如果本地文件系统存在某个文件或目录则退出该阶段继续执行下面的阶段,否则内部重定向到最后一个参数指定的location或返回指定的返回码。该阶段也不能注册handler。

示例:

server {
   listen 80;
   server_name www.imcati.com;
   root /usr/share/nginx/html;
   try_files $uri /index.php /index.html;
}

以上配置会按顺序检查文件是否存在,若存在则直接返回,顺序:$uri --> /index.php -->/index.html

查看返回:curl http://www.imcati.com/12345

nginx处理请求的11个阶段

nginx处理请求的11个阶段

CONTENT 阶段:

    CONTENT阶段有些特殊,它不像其他阶段只能执行固定的handler链,还有一个特殊的content_handler,每个location可以有自己独立的content handler,而且当有content handler时,CONTENT阶段只会执行content handler,不再执行本阶段的handler链。

    默认情况下,Nginx会在CONTENT阶段的handler链挂上index模块,静态文件处理模块等的handler。另外模块还可以设置独立的content handler,比如ngx_http_proxy_module的proxy_pass指令会设置一个名为ngx_http_proxy_handler的content handler。

    CONTENT 阶段任务是生成响应内容并输出HTTP响应。如echo指令,echo_ecxec,proxy_pass,echo_location及content_by_lua都运行在此阶段。注意,与rewrite和access阶段不同,content阶段不同模块的配置指令不能一起混合使用。向 content 阶段注册配置指令本质上是在当前的 location 配置块中注册所谓的“内容处理程序”。而每一个 location 只能有一个"内容处理程序",因此,当在 location 中同时使用多个模块的 content 阶段指令时,只有其中一个模块能成功注册“内容处理程序”。所以应当避免在同一个 location 中使用多个模块的 content 阶段指令。

    content阶段包含三个静态资源服务模块,ngx_index,ngx_autoindex,ngx_static用于当在location未使用任何content阶段的指令时处理URL请求。ngx_index和ngx_autoindex只作用于已/结尾的URI,其他由ngx_static执行。

    ngx_index使用index指令用于查找首页文件,配合root指令实现,当找到文件后触发内部跳转而不是直接返回该文件,若都不存在则返回403。ngx_autoindex用于开启目录索引autoindex on,当index指定的首页文件不存在时返回该目录索引。   

    ngx_static处理所有的静态资源的请求,    /VAR/WWW/目录下有index.html文件,如:

    location / {

        root /var/www/;

    }

    当请求index.html时,该location匹配上,并最终由ngx_static处理,返回该index.html。若没有root指定根目录,则使用安装nginx时使用的--prefix目录.

示例:

server {
   listen 80;
   server_name www.imcati.com;
   root /usr/share/nginx/html;
   location / {
     index index.html;
   }
}

查看返回:curl http://www.imcati.com/

nginx处理请求的11个阶段 

LOG阶段
    LOG阶段主要的目的就是记录访问日志,进入该阶段表明该请求的响应已经发送到系统发送缓冲区。另外这个阶段的handler链实际上并不是在ngx_http_core_run_phases函数中执行,而是在释放请求资源的ngx_http_free_request函数中运行,这样做的原因实际是为了简化流程,因为ngx_http_core_run_phases可能会执行多次,而LOG阶段只需要再请求所有逻辑都结束时运行一次,所以在ngx_http_free_request函数中运行LOG阶段的handler链是非常好的选择。