美文网首页
Nginx的配置指令执行的顺序 11 个阶段

Nginx的配置指令执行的顺序 11 个阶段

作者: 邵增卫 | 来源:发表于2020-01-09 14:05 被阅读0次

Nginx的配置指令执行不是按照配置的先后顺序执行,二十分为11 个阶段post-read、server-rewrite、find-config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content 以及 log , Nginx配置文件中的所有指令是按照上面11个阶段的顺讯执行。

  1. post-read
    该阶段是在Nginx读取并解析完请求头(request headers)之后就立即开始执行的,标准模块的ngx_realip就是在post-read阶段注册了的处理程序。它的功能是迫使 Nginx 认为当前请求的来源地址是指定的某一个请求头的值。
    ngx_realip 主要有三个指令 set_real_ip_from,real_ip_header,real_ip_recursive 。这三个指令可以直接配置在server下,下面是ngx_realip官网 列举的一个配置的示例。
 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;

set_real_ip_from的值可以是一个ip,也可以是一个ip段,表示当请求是某一个ip时,使用real_ip_header的值替换remote_addr中的值,并将原来的值保留在realip_remote_addr 和 realip_remote_port中。

    log_format main '"$remote_add" - "$remote_user" - "$realip_remote_addr" - "$realip_remote_port"'
    server {
        listen   9090;
        access_log  logs/access_9090.log main;
        set_real_ip_from 127.0.0.1;
        real_ip_header  IP;
        
        location /first {
             rewrite ^(.*)$ /index.html break; 
       }
    }

运行命令

    curl -H"IP: 192.2.2.2" http://localhost:9090/first

查看日志其中的remote_addr 已经改为 192.2.2.2,而 $realip_remote_addr 是127.0.0.1。 当Header IP是非法的IP地址,则remote_addr 不改变。
** 注意 real_ip_header 的默认值为 X-Real-IP

下面我们着重看一下 real_ip_recursive(默认值为off)
1) real_ip_header 的http header中仅有一个ip时,则直接替换remote_addr。
2) real_ip_header 的http header 中有多个ip时
- real_ip_recursive 为off时, 则用第一个ip替换remote_addr
- real_ip_recursive 为on时, 则用第一个不在set_real_ip_from 的ip替换remote_addr
例如nginx配置如下:

              set_real_ip_from 127.0.0.1;
              set_real_ip_from 192.178.2.2;
              real_ip_header  IP;
              real_ip_recursive on;

请求命令为

      curl -H"IP: 192.168.2.3 192.178.2.2" http://localhost:8080/first

remote_addr 被替换为 192.168.2.3 (注意这里的顺序是从右至左)。当real_ip_recursive为off时 remote_addr被替换为 192.178.2.2

注意: ngx_realip的三个指令都放在 server下,不要放在location下,我在mac 1.15.5上进行测试时,location下的配置是不生效的。

  1. server-rewrite
    当 ngx_rewrite 模块的配置指令直接书写在 server 配置块中时,都是运行在 server-rewrite 阶段。
    ngx_rewrite的指令主要有一下几个:
    break
    if
    return
    rewrite
    rewrite_log
    set
    uninitialized_variable_warn

例如nginx有以下配置

    log_format  main '"$remote_addr" -  "$b"';
    server {
        access_log logs/host_8080.log main; 
        listen 8080;
        location /first {
              set $a "$b world";
              rewrite ^(.*)$ /index.html;
        }

      set $b "Hello";
    }

访问/first之后,日志中输出 Hello world; 这是因为set $b "Hello"; 定义在server模块下,所以在server-rewrite阶段已经执行。所以在location块中定义的才能执行的结果才是正确的。
后面再rewrite阶段会详细讲解这几个命令。

  1. find-config
    find-config 阶段不支持NGINX模块注册处理程序,而是由Nginx核心模块来完成当前请求的URL与location配置块之间的匹配工作。在Nginx接收到请求到此阶段之前,location配置块中包括locaction指令本身的所有指令都没有执行。也就是说,post-read和server-rewrite阶段执行的仅仅是server配置块以及更外层作用域中的配置指令。

  2. rewrite
    rewrite阶段又可以分为两个小的阶段
    1> 在server配置块中,按照顺序执行ngx_rewrite模块的指令
    2> 在find-config阶段,nginx内核根据server配置块中所有location的配置为请求匹配到合适的location配置块,在rewrite阶段,location配置块中的配置的ngx_rewrite模块的指令将被执行。

下面我们详细看一下ngx_rewrite中的7个指令:
1) rewrite (基本语法: rewrite regex replacement [flag]; 可以配置在server,location和if配置块下)
regex是RCPE风格的,如果regex匹配URI,nameURI就会被替换成replacement,replacement就是新的URI,如果同一个配置块下有多个rewrite配置指令,匹配并不会终止,而是继续用新的URI(也就是上一次匹配到的replacement)继续进行匹配,直至返回最后一个匹配。如果在匹配到之后想要停止匹配直接返回,可是使用flag参数(将其设置为break)。

注意 : 如果匹配到replacement中有关于协议的东西,例如http://或者https://等,处理将终止,直接返回客户端302(Temporary redirect)。
如果Nginx返回给客户端30x,浏览器在接收到这个状态码时,会根据response中的Location响应头再发起一次请求;如果不是30x状态码,所有的跳转只是在nginx内部完成,有兴趣的可以将nginx的日志调为debug看一下整个跳转过程。
举一个例子

    location /first {
        rewrite ^(.*)$ /test;
        rewrite /test  /index.html;
        rewrite /test /second.html;
    }

当请求 curl -I http://localhost:8080/first时,nginx接到请求之后,匹配到/first 根据第一个rewrite指令,将URI替换为/test, 然后继续执行第二个rewrite指令,将URI替换为/index.html,之后继续执行第三个指令,返现regex(/test)与新的URI(/index.html)并不匹配,所以最终我们将会看到index.html的返回结果。开启debug日志,可以看到详细的跳转过程。

再举一例

    server {
        rewrite  ^(.*)$  https://$host:$server_port$request_uri;
        
        location /first {
            rewrite ^(.*)$ /test;
        }
    }

当请求curl -I http://localhost:8080/first时,首先会执行server块下的rewrite,所有请求都返回302 (Location:https://127.0.0.1:8080/first)。

下面讲一下rewrite的第三个参数flag:
1.1 ) last
如果有last参数,那么停止处理任何rewrite相关的指令,立即用替换后的新URI开始下一轮的location匹配。

1.2) break
停止处理任何rewrite的相关指令,就如同break 指令本身一样。

last的break的相同点在于,立即停止执行所有当前上下文的rewrite模块指令;不同点在于last参数接着用新的URI马上搜寻新的location,而break不会搜寻新的location,直接用这个新的URI来处理请求,这样能避免重复rewite。因此,在server上下文中使用last,而在location上下文中使用break。

1.3) redirect
replacement 如果不包含协议,仍然是一个新的的URI,那么就用新的URI匹配的location去处理请求,不会返回30x跳转。但是redirect参数可以让这种情况也返回30x(默认302)状态码,就像新的URI包含http://和https://等一样。这样的话,浏览器看到302,就会再发起一次请求,真正返回响应结果的就是这第二个请求

1.4) permanent
和redirect参数一样,只不过直接返回301永久重定向

虽说URI有了新的,但是要拼接成完整的URL还需要当前请求的scheme,以及由server_name_in_redirect和port_in_redirect指令决定的HOST和PORT.

还有一个比较有意思的应用,就是如果replacement中包含请求参数,那么默认情况下旧URI中的请求参数也会拼接在replacement后面作为新的URI,如果不想这么做,可以在replacement的最后面加上?。

2) break (基本语法: break; 可以配置在server,location 和if配置块下)
停止任何处理rewrite的相关指令。如果出现在location里面,name所有后面的rewrite模块都不会执行,也不发起内部重定向,而是直接用新的URI处理请求。
例如有如下配置

  server {
      rewrite   ^/server/break$   /hello1 break;
      rewrite   ^/server/last$   /hello1 last;
      location /hello {
            rewrite ^(.*)$ /hello1;
            break;
      }
     
     location hello1 {
         rewrite ^(.*)$ /index.html last;     
      }

     location / {
          root html;  
    }
  }

访问 curl -I http://localhost:8080/server/break,将返回404;
请求curl -I http://localhost:8080/server/last将返回index.html
访问 curl -I http://localhost:8080/hello,将返回 404;
请求 curl -I http://localhost:8080/hello1,将返回index.html。

综合以上的实验,可以肯定的是在server中配置块中如果使用break指令,下面所有的location将不会执行,而last会进行下一次内部跳转。location中的break则是直接退出,返回URI的结果; last 进入下一次内部跳转,该location内的rewrite配置指令不再执行。

3) if (基本语法:if(condition) {....}) 可以配置在server和location配置块中
根据condition是true和false决定是否加载 花括号中的配置,花括号中的配置可以继承外面的配置,也可以对外面的指令进行重写。
condition 可以是自定义变量或者系统变量本身,也可以是表达式:
a. condition 为变量本身时, 0: false 非0 : true
b. 变量可以通过"=" 、"!=" 与字符串比较
c. 匹配正则表达式 ~ !~ (大小写敏感) ~* !~* (大小写不敏感)
d. -f -d -e -x !-f !-d !-e !-x 等检验文件或者目录存在或者文件属性

4) return (基本语法: return code [text]; return code URL; return URL; 可以配置在server,location和if配置块中)
停止任何的进一步处理,并且将指定状态码返回给客户端。如果状态码为444(此状态码是非标准的),那么直接关闭此TCP连接。

return的参数有四种形式:

  • return code 此时,响应内容就是nginx所默认的,比如503 Service Temporarily Unavailable; 如果是444那就直接关闭TCP连接,也可以是其他值(644等),但是没啥意义
  • return code text 因为要带响应内容,因此code不能是具有跳转功能的30x
  • return code URL 此时URI可以为URI做内部跳转,也可以是具有“[http://”或者“https://”等协议的绝对URL,直接返回客户端,而code是30x(301, 302, 303, 307,308)
  • return URL 此时code默认为302,而URL必须是带“http://”等协议的绝对URL

5) set (基本语法: set $var value; 可以配置在server,location和if配置块中)
这个指令可以用来自定义变量,也可以改变系统变量的值。

6) rewrite_log (基本语法: rewrite_log on | off; 可以配置在http,server,location和if配置块中)
如果开启on,name当rewrite时,会产生一个notice基本的日志;否则不产生日志。可以在调试的时候将其设置为on。

  1. post-rewrite
    post-rewrite阶段和find-config阶段一样,没有Nginx模块注册处理程序,由nginx核心完成rewrite阶段所要求的内部跳转操作(当rewrite阶段有内部跳转的配置指令时)。内部跳转本质上是跳转到find-config阶段,使用新的URI重新匹配location。
    例如nginx有如下配置:
  server {
    listen 8080;
    location /first {
        rewrite ^ /index.html;
    }
    location / {
          root html;
    }
  }

请求 curl -I http://localhost:8080/first时,先找到/firstlocation,在/firstlocation中rewrite URI 为/index.html进入post-rewrite阶段,进行一次内部跳转,回到find-config阶段,使用新的/index.html做为URI重新匹配location至 location /
这里有一个问题,为什么不在rewrite阶段执行内部跳转?答案就是为了在一个location中支持反复改写URI。试想一下如果有以下配置。

     location /first {
          rewrite ^ /second;
          rewrite /second /third;
      }

如果在rewrite阶段直接做内部跳转,那么 第二个rewrite将不能执行,我们必须再声明一个 /second location进行URI 重写。
想证实这一点很简单,我们做一个简单的例子:

  server {
      listen 8080;
      
       location /first {
          rewrite ^ /second;
          rewrite /second /third;
      }
      location /second {
          return 200 "second content";
      }
      location /third {
          return 200 "third content";
      }
  }

请求curl http://localhost:8080/first之后将得到 "third content"相应值,同时可以在日志中看到如下日志。

  using configuration /first
  using configuration /third

注意一下,server配置块下的rewrite命令在server-rewrite阶段执行。

  1. preaccess
    标准模块 http_limit_req_module 的配置项limit_req_zone 和limit_req_conn 在此阶段运行。limit_req_zone 可以控制请求的访问频度,即速率限制,采用的漏桶算法 “leaky bucket”;limit_req_conn 可以限制访问的并发度,可以根据源IP限制单用户并发访问的连接数或连接到该服务的总并发连接数。

nginx限制请求是一个比较复杂的模块,后面会单独解说。

  1. access
    标准模块 ngx_http_access_module、第三方模块 ngx_auth_request 、 ngx_lua 就运行在这个阶段。
    ngx_http_access_module主要有两个指令:
  • deny (基本语法deny address | CIDR | unix: | all; 可以配置在 http, server, location, limit_except)
  • allow (基本语法allow address | CIDR | unix: | all; 可以配置在 http, server, location, limit_except)
  1. post-access
    post-access 阶段主要用于配合 access 阶段实现标准 ngx_http_core 模块提供的配置指令 satisfy 的功能。
    对于多个 Nginx 模块注册在 access 阶段的处理程序, satisfy 配置指令可以用于控制它们彼此之间的协作方式。比如模块 A 和 B 都在 access 阶段注册了与访问控制相关的处理程序,那就有两种协作方式,一是模块 A 和模块 B 都得通过验证才算通过,二是模块 A 和模块 B 只要其中任一个通过验证就算通过。第一种协作方式称为 all 方式(或者说“与关系”),第二种方式则被称为 any 方式(或者说“或关系”)。默认情况下,Nginx 使用的是 all 方式。下面是一个例子:
  location /post_access {
      satisfy all;
      deny all;
      access_by_lua 'ngx.exit(ngx.OK)' ;
      return 200 "hello world";
    }

在接口/post_access 中同时配置了ngx_access 和ngx_lua两个模块,这样access阶段就由两个模块一起检查,其中deny all 会让ngx_access模块处理程序拒绝当前请求,而语句access_by_lua 'ngx.exit(ngx.OK)' ; 则总是允许访问。当satify指令为all时,当前的请求会被拒绝,返回403(forbidden)。

  1. try-files
    这个阶段用来实现标准配置try-files指令的功能,不支持Nginx模块注册处理程序。
    try-files接受两个以上的任意数量的参数,每一个参数都制定了一个URI。加入有指令try-files /uri1 /uri2 ..... /uriN; try-files阶段,一次把前面的N-1个参数映射为系统目录中的文件(uri不是以/结尾)或者目录(uri以/结尾)。如果任意一个文件或者目录存在,try-files阶段把当前请求的URI改为该对象对应的URI参数(不会包含末尾的/,也不会发生内部跳转)。如果前N-1个都不存在,则将URI改为最后一个参数,并发起内部跳转。
    例如如下配置
server {
    listen 8080;
    root /var/www;
    
    location /first {
      try_files /noExist /index.html /second;
    }
    
    location /first1 {
      try_files /noExist /second;
    }
    
    location first2 {
      try_files /second /index.html;
    }

    location /second {
        return 200 "second content";
    }

    location /third {
      try_files /dir/ /second; 
    }
}
  • 请求curl -I http://localhost:8080/first, 返回index.html
  • 请求curl -I http://localhost:8080/first1, 返回 "second content"
  • 请求curl -I http://localhost:8080/third, 返回302,Location为 http://localhost:8080/dir/,浏览器默认会显示dir目录中的index.html。
  • 请求curl -I http://localhost:8080/first2, 返回index.html
  1. content
    content阶段主要是处理内容输出的阶段,是整个模型中比较靠后的阶段,但这个阶段是非常重要的一个阶段,有大量的模块与指令,例如proxy,三方的echo,content_by_lua等模块都是在这个阶段执行。注意Nginx 模块在向 content 阶段注册配置指令时,其实就是向location 配置块中注册所谓的“内容处理程序”(content handler)。每一个 location 只能有一个“内容处理程序”,因此,当在 location 中同时使用多个模块的 content 阶段指令时,只有其中一个模块能成功注册“内容处理程序”。例如 echo 和 content_by_lua 如果同时注册,最终只会有一个生效,但具体是哪一个生效是不稳定的。
    proxy模块是Nignx一个重要的功能,会在后面单独说明。

  2. log
    输出日志

相关文章

网友评论

      本文标题:Nginx的配置指令执行的顺序 11 个阶段

      本文链接:https://www.haomeiwen.com/subject/ogyyactx.html