nginx phases 介绍

一、nginx的11个phases

一个请求经过nginx处理的过程中,会经过一系列的阶段(phases),下面这个表格列出了nginx的所有phases,每个阶段可选的退出方式,包含的模块和对应的指令

phaseoptional exitsmodules / directivesdescription
NGX_HTTP_POST_READ_PHASE HttpRealIpModule读取请求内容阶段
NGX_HTTP_SERVER_REWRITE_PHASE (server rewrite) HttpRewriteModule / rewrite请求地址重写阶段
NGX_HTTP_FIND_CONFIG_PHASE(location selection) HttpCoreModule / location配置查找阶段
NGX_HTTP_REWRITE_PHASE Location(location rewrite)location selection, 
finalize request
HttpLuaModule / set_by_lua rewrite_by_lua ,HttpCoreModule/set,HttpRewriteModule / rewrite请求地址重写阶段
NGX_HTTP_POST_REWRITE_PHASE 不注册其他模块请求地址重写提交阶段
NGX_HTTP_PREACCESS_PHASE degradation, NginxHttpLimitZoneModule / limit_zone, 
HttpLimitReqModule / limit req, HttpRealIpModule(该模块不建议注册在改phase)
访问权限检查准备阶段
NGX_HTTP_ACCESS_PHASEfinalize request标准模块HttpAccessModule / allow, deny, 第三方NginxHttpAuthBasicModule / auth_basic,HttpLuaModule/access_by_lua访问权限检查阶段
NGX_HTTP_POST_ACCESS_PHASE 不支持 Nginx 模块注册处理程。post-access 阶段主要用于配合 access 阶段实现标准HttpCoreModule模块提供的配置指令satisfy的功能,该指令可以用于控制access阶段的指令彼此之间的协作方式访问权限检查提交阶段
NGX_HTTP_TRY_FILES_PHASElocation selectionHttpCoreModule / try_files配置项try_files处理阶段
NGX_HTTP_CONTENT_PHASE HttpAutoindexModule / autoindex,HttpCoreModule / Core, HttpDavModule / DAV,
HttpEmptyGifModule / EmptyGif, HttpFcgiModule / FastCGI, HttpFlvStreamModul / FLV, 
HttpGzipStaticModule / gzip_static, HttpIndexModule / index,
HttpMemcachedModule / memcached, EmbeddedPerlModule / perl,
HttpProxyModule / proxy, HttpProxyModule / random_index,
HttpScgiModule / scgi, HttpStubStatusModule / stub_status, 
HttpUwsgiModule / uwsgi HttpLuaModule / content_by_lua,
HttpCoreModule / proxy_pass
内容产生阶段
NGX_HTTP_LOG_PHASE HttpLogModuel / access_log日志模块处理阶段

二、各个phase说明:

post read phase:

nginx读取并解析完请求头之后就进入了post_read 阶段,它位于uri被重写之前,这个阶段允许nginx改变请求头中ip地址的值,相关模块HttpRealIpModule

server_rewrite phase:

这个阶段主要进行初始化全局变量,或者server级别的重写。如果把重写指令放到 server 中,那么就进入了server rewrite 阶段。(重写指令见rewrite phase)

find config phase:

这个阶段使用重写之后的uri来查找对应的location,值得注意的是该阶段可能会被执行多次,因为也可能有location级别的重写指令。这个阶段并不支持 Nginx 模块注册处理程序,而是由 Nginx 核心来完成当前请求与 location 配置块之间的配对工作

rewrite phase:

如果把重写指令放到 location中,那么就进入了rewrite phase,这个阶段是location级别的uri重写阶段,重写指令也可能会被执行多次;

重写指令有 HttpRewriteModule 的set指令,rewrite指令,HttpLuaModule的 set_by_lua指令, ngx_set_misc模块的set_unescape_uri指令,另外HttpRewriteModule的几乎所有指令都属于rewrite阶段。

到此,思考一个问题: 既然不用module的不同重写指令到可以在这个phase,那么这些指令是否可以在同一个location并存,如果可以,那么他们的执行顺序是怎么样的?

例子1 :
思考下面例子的输出的结果是什么?

 location /test {
        set $a 32;
        set $b 56;
        set_by_lua $c "return ngx.var.a + ngx.var.b";
        set $d "$a + $b = $c";
        echo $d;
    }

当我们访问 http://localhost/test时,输出结果为 32 + 56 = 88 ,应该是和我们预期一致的。

但是这并不能证明所有属于同一个phases的不同的module的指令在一个phase中并存时一定是按照顺序执行下来的。 事实上,上面提到的这些第三方模块都采用了特殊的技术,将它们自己的配置指令“注入”到了 HttpRewriteModule的指令序列中(它们都借助了 Marcus Clyne 编写的第三方模块 ngx_devel_kit)。换句话说,更多常规的在 Nginx 的 rewrite 阶段注册和运行指令的第三方模块就没那么幸运了。这些“常规模块”的指令虽然也运行在 rewrite 阶段,但其配置指令和 HttpRewriteModule(以及同一阶段内的其他模块)都是分开独立执行的。在运行时,不同模块的配置指令集之间的先后顺序一般是不确定的(严格来说,一般是由模块的加载顺序决定的,但也有例外的情况)。比如 A 和 B 两个模块都在 rewrite 阶段运行指令,于是要么是 A 模块的所有指令全部执行完再执行 B 模块的那些指令,要么就是反过来,把 B 的指令全部执行完,再去运行 A 的指令。除非模块的文档中有明确的交待,否则用户一般不应编写依赖于此种不确定顺序的配置。还有不少第三方模块,ngx_array_var 以及用于加解密用户会话(session)的 ngx_encrypted_session,也都可以和HttpRewriteModule的指令无缝混合工作。标准 HttpRewriteModule的应用是如此广泛,所以能够和它的配置指令混合使用的第三方模块是幸运的。不能和HttpRewriteModule混合使用的指令在实际使用的过程要引起注意,它的输出是不确定的,这与他们在配置文件中的顺序无关。

结论:作用域为同一个phase的不同modules的指令,如果modules之间做了特殊的兼容,则它们按照指令在配置文件中出现的顺序依次执行下来

例子2:
思考下面这个输出什么?

location /test {
        set $value dog;
        more_set_input_headers "X-Species: $value";
        set $value cat;
        echo "X-Species: $http_x_species";
    }

访问:curl 'http://localhost/test'
输出:X-Species: cat
是不是和你的预期有点儿不一样呢?

说明:第三方模块 ngx_headers_more 提供了一系列配置指令,用于操纵当前请求的请求头和响应头。其中有一条名叫 more_set_input_headers 的指令可以在 rewrite 阶段改写指定的请求头(或者在请求头不存在时自动创建)。该指令的文档中有这么一行标记 phase: rewrite tail,是说这条指令总是运行在 rewrite 阶段的末尾。显然,写在 more_set_input_headers 指令之后的 set $value cat 语句却先执行了。也就是说属于HttpRewriteModule的set指令先执行完了,才执行ngx_header_more 的set_input_headers指令。
结论:即使运行在同一个请求处理阶段,分属不同模块的配置指令也可能会分开独立运行(除非像 ngx_set_misc 等模块那样针对 ngx_rewrite 模块提供特殊支持)。换句话说,在单个请求处理阶段内部,一般也会以 Nginx 模块为单位进一步地划分出内部子阶段。下面的例子3同例子2:

例子3:

location /test {
         set $a 1;
         rewrite_by_lua "ngx.var.a = ngx.var.a + 1";
         set $a 56;    
         echo $a;
     }

访问: curl 'http://localhost/test'
输出: 57

说明:HttpLuaModule的rewrite_by_lua 指令也是处在 rewrite tail phase,它也会在rewrite 阶段的末尾执行。因此HttpRewriteModule的所有set执行完后,才执行它。
显然,rewrite_by_lua 指令的行为不同于我们前面在 (二) 中介绍过的 set_by_lua 指令。
小伙伴们可能要问,既然 more_set_input_headers 和 rewrite_by_lua 指令都运行在 rewrite 阶段的末尾,那么它们之间的先后顺序又是怎样的呢?答案是:不一定。我们应当避免写出依赖它们二者间顺序的配置。

结论:作用域在同一个phase的不同modules的指令,如果没有做特殊兼容处理,则它们指令的执行顺序与指令在配置中出现的顺序无关,结果具有不确定性

post rewrite phase:

location级别重写的下一阶段,用来检查上阶段是否有uri重写,并根据结果跳转到合适的阶段;

preaccess phase:

访问权限控制的前一阶段,该阶段在权限控制阶段之前,一般也用于访问控制,比如限制访问频率,链接数等;相关模块/指令 :NginxHttpLimitZoneModule / limit_zone, 
HttpLimitReqModule / limit req, HttpRealIpModule

access phase:

访问权限控制阶段,比如基于ip黑白名单的权限控制,基于用户名密码的认证控制等;相关模块/指令 HttpAccessModule / allow, deny, NginxHttpAuthBasicModule / auth_basic。 HttpAccessModule提供的 allow 和 deny 配置指令可用于控制哪些 IP 地址可以访问,哪些不可以。HttpAccessModule模块还支持所谓的“CIDR 记法”来表示一个网段,例如 169.200.179.4/24 则表示路由前缀是 169.200.179.0(或者说子网掩码是 255.255.255.0)的网段。

思考下下面两个例子(例子5,例子6)
例子4:

    location /hello {
        allow 127.0.0.1;
        deny all; 
        echo "hello world";
    }

本机访问:curl  http://localhost/hello  ,输出:hello world
其他机器访问:curl  http://localhost/hello  ,报403错误

例子5:

    location /hello {
        deny all; 
        allow 127.0.0.1;
        echo "hello world";
    }

本机访问:curl  http://localhost/hello  ,报403错误
其他机器访问:curl  http://localhost/hello  ,报403错误

例5和例6的区别在于deny all ,和allow 127.0.0.1 这两条指令的顺序不同。但例5中/hello 只允许从本机(IP 地址为保留的 127.0.0.1)访问,而从其他 IP 地址访问都会被拒(返回 403 错误页)。而例6中被配置为任何IP访问都会返回403错误。
原因说明:同属于HttpAccessModule这个模块的多条配置指令之间是按顺序执行的,直到遇到第一条满足条件的指令就不再执行后续的 allow 和 deny 指令。如果首先匹配的指令是 allow,则会继续执行后续其他模块的指令或者跳到后续的处理阶段;而如果首先满足的是 deny 则会立即中止当前整个请求的处理,并立即返回给客户端 403 错误页。

结论:同一个phase的同一个module内的多条指令的执行顺序由这个module自己来定义。

因为 HttpAccessModule的指令运行在 access 阶段,而 access 阶段又处于 rewrite 阶段之后,所以前面我们见到的所有那些在 rewrite 阶段运行的配置指令,都总是在 allow 和 deny 之前执行,而无论它们在配置文件中的书写顺序是怎样的。所以,为了避免阅读配置时的混乱,我们应该总是让指令的书写顺序和它们的实际执行顺序保持一致。

post access phase:

访问权限控制的后一阶段,该阶段根据权限控制阶段的执行结果进行相应处理;

try files phase:

HttpCoreModule的try_files指令的处理阶段,如果没有配置try_files指令,则该阶段被跳过; 该指令作用域: server ,location。
当try_files用于server阶段时一般是初始化作用,用来加载一些文件。

语法: try_files file1,file2,..,fileN-1 ... ,fallback 或者try_files file1,file2,..,fileN = code
用来顺序检查file1,file2,...fileN-1是否存在,如果最后一个字符为/表示这是一个目录。只要找到一个file存在,则进入到content phase,输出内容。如果前N-1个参数代表的file都不存在,此时最后一个参数发挥作用,最后一个参数用于内部跳转,并且只有最后一个参数是用作内部跳转,因此最后一个参数必须存在,否则将会引发一个内部错误。前面的file参数只是设置uri指向,不会引发内部跳转。此外注意: try_files和rewrite不同,rewrite指令会自动保存原请求的参数$args,而try_files最后的fallback参数如果需要带上请求的参数,则需要明确指出,如:

try_files $uri $uri/ /index.php?q=$uri&$args

content phase:

内容生成阶段,该阶段产生响应,并发送到客户端; 这个阶段相关的模块和指令较多。这里仅拿HttpLuaModule的content_by_lua和HttpEchoModule的echo指令来举个例子再次证明上述谈论到的一个结论。
例子:

 location /test {
          set $a 123;
          set $b 456;
          echo $b;
          content_by_lua '
             ngx.say(ngx.var.a)
           ';
    }

访问: curl  http://localhost/test
输出: 123 
即只有content_by_lua里面的ngx.say执行了,外面的echo $b指令没有执行。echo和content_by_lua分别属于两个module的指令,他们的作用phase都是content phase。这两个module不兼容,
这两条指令不是顺序执行,至于执行结果是什么,不确定,这里是只有content_by_lua被执行了。再次证明之前的结论:作用域在同一个phase的不同modules的指令,如果没有做特殊兼容处理,则它们指令的执行顺序与指令在配置中出现的顺序无关,结果具有不确定性,需要尽量避免这种情况出现。

 location /test_echo {
          content_by_lua '
             ngx.say(ngx.var.a)
           ';
          set $a 123;
          set $b 456;
    }

log phase:

日志记录阶段,该阶段记录访问日志;

三、小结:

  • 11个phases粗略介绍完了。这11个phases,不是每个请求都会经历所有的11个phase。有可能某些phase没有经历,如果请求内部包含子请求,某些phase可能会出现多次。
  • 在单个请求的处理过程中,前面的phase总是在后面phase之前执行。这里的前后值得是本文phase的介绍顺序,而不是phase相关的指令在配置文件中出现的顺序。
    例如,rewrite phase总是在content phase之前执行,与指令在配置文件中出现顺序无关,下面的例子
    location /test {
        set $a 11;
        echo $a; 
        set $a 22;
        echo $a;
    }
    

    实际执行顺序为set $a 11;set $a 22;echo $a;echo $a;
    访问:curl http://localhost/test
    输出:
    22 
    22
    理由是rewrite phase总是在content phase之前执行。所以,为了避免阅读配置时的混乱,我们应该总是让指令的书写顺序和它们的实际执行顺序保持一致。
  • 作用域为同一个phase的不同modules的指令,如果modules之间做了特殊的兼容,则它们按照指令在配置文件中出现的顺序依次执行下来
  • 作用域在同一个phase的不同modules的指令,如果没有做特殊兼容处理,则它们指令的执行顺序与指令在配置中出现的顺序无关,结果具有不确定性
  • 同一个phase的同一个module内的多条指令的执行顺序由这个module自己来定义。


四、附录

由于HttpLuaModule是一个非常有用的module,这里附Nginx下Lua处理阶段包含的常见指令及其适用范围

init_by_lua            http
set_by_lua             server, server if, location, location if
rewrite_by_lua         http, server, location, location if
access_by_lua          http, server, location, location if
content_by_lua         location, location if
header_filter_by_lua   http, server, location, location if
body_filter_by_lua     http, server, location, location if
log_by_lua             http, server, location, location if
timer


init_by_lua:
在nginx重新加载配置文件时,运行里面lua脚本,常用于全局变量的申请。
例如lua_shared_dict共享内存的申请,只有当nginx重起后,共享内存数据才清空,这常用于统计。

set_by_lua:
设置一个变量,常用与计算一个逻辑,然后返回结果
该阶段不能运行Output API、Control API、Subrequest API、Cosocket API

rewrite_by_lua:
在access阶段前运行,主要用于rewrite

access_by_lua:
主要用于访问控制,能收集到大部分变量,类似status需要在log阶段才有。
这条指令运行于nginx access阶段的末尾,因此总是在 allow 和 deny 这样的指令之后运行,虽然它们同属 access 阶段。



content_by_lua:
阶段是所有请求处理阶段中最为重要的一个,运行在这个阶段的配置指令一般都肩负着生成内容(content)并输出HTTP响应。

header_filter_by_lua:
一般只用于设置Cookie和Headers等
该阶段不能运行Output API、Control API、Subrequest API、Cosocket API

body_filter_by_lua:
一般会在一次请求中被调用多次, 因为这是实现基于 HTTP 1.1 chunked 编码的所谓“流式输出”的。
该阶段不能运行Output API、Control API、Subrequest API、Cosocket API

log_by_lua:
该阶段总是运行在请求结束的时候,用于请求的后续操作,如在共享内存中进行统计数据,如果要高精确的数据统计,应该使用body_filter_by_lua。
该阶段不能运行Output API、Control API、Subrequest API、Cosocket API


五、参考资料:

http://wiki.nginx.org/

http://wiki.nginx.org/Phases

http://blog.sina.com.cn/s/blog_6d579ff40100xpff.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值