Nginx 的 content 阶段是所有请求处理阶段中最为重要的一个,因为运行在这个阶段的配置指令一般都肩负着生成“内容”(content)并输出 HTTP 响应的使命。正因为其重要性,这个阶段的配置指令也异常丰富,例如前面我们一直在示例中广泛使用的 echo 指令,在 Nginx 变量漫谈(二) 中接触到的 echo_exec 指令,Nginx 变量漫谈(三) 中接触到的 proxy_pass 指令,Nginx 变量漫谈(五) 中介绍过的 echo_location 指令,以及 Nginx 变量漫谈(七) 中介绍过的 content_by_lua 指令,都运行在这个阶段。

content 阶段属于一个比较靠后的处理阶段,运行在先前介绍过的 rewrite 和 access 这两个阶段之后。当和 rewriteaccess 阶段的指令一起使用时,这个阶段的指令总是最后运行,例如:

    location /test {
        # rewrite phase
        set $age 1;
        rewrite_by_lua "ngx.var.age = ngx.var.age + 1";
 
        # access phase
        deny 10.32.168.49;
        access_by_lua "ngx.var.age = ngx.var.age * 3";
 
        # content phase
        echo "age = $age";
    }

这个例子中各个配置指令的执行顺序便是它们的书写顺序。测试结果完全符合预期:

    $ curl 'http://localhost:8080/test'
    age = 6

即使改变它们的书写顺序,也不会影响到执行顺序。其中,set 指令来自 ngx_rewrite 模块,运行于 rewrite阶段;而 rewrite_by_lua 指令来自 ngx_lua 模块,运行于 rewrite 阶段的末尾;接下来,deny 指令来自ngx_access 模块,运行于 access 阶段;再下来,access_by_lua 指令同样来自 ngx_lua 模块,运行于access 阶段的末尾;最后,我们的老朋友 echo 指令则来自 ngx_echo 模块,运行在 content 阶段。

这个例子展示了通过同时使用多个处理阶段的配置指令来实现多个模块协同工作的效果。在这个过程中,Nginx 变量则经常扮演着在指令间乃至模块间传递(小份)数据的角色。这些配置指令的执行顺序,也强烈地受到请求处理阶段的影响。

进一步地,在 rewrite 和 access 这两个阶段,多个模块的配置指令可以同时使用,譬如上例中的 set 指令和 rewrite_by_lua 指令同处 rewrite 阶段,而 deny 指令和 access_by_lua 指令则同处 access 阶段。但不幸的是,这通常不适用于 content 阶段。

绝大多数 Nginx 模块在向 content 阶段注册配置指令时,本质上是在当前的 location 配置块中注册所谓的“内容处理程序”(content handler)。每一个 location 只能有一个“内容处理程序”,因此,当在location 中同时使用多个模块的 content 阶段指令时,只有其中一个模块能成功注册“内容处理程序”。考虑下面这个有问题的例子:

    ? location /test {
    ?     echo hello;
    ?     content_by_lua 'ngx.say("world")';
    ? }

这里,ngx_echo 模块的 echo 指令和 ngx_lua 模块的 content_by_lua 指令同处 content 阶段,于是只有其中一个模块能注册和运行这个 location 的“内容处理程序”:

    $ curl 'http://localhost:8080/test'
    world

实际运行结果表明,写在后面的 content_by_lua 指令反而胜出了,而 echo 指令则完全没有运行。具体哪一个模块的指令会胜出是不确定的,例如把上例中的 echo 语句和 content_by_lua 语句交换顺序,则输出就会变成hello,即 ngx_echo 模块胜出。所以我们应当避免在同一个 location 中使用多个模块的 content 阶段指令。

将上例中的 content_by_lua 指令替换为 echo 指令就可以如愿了:

    location /test {
        echo hello;
        echo world;
    }

测试结果证明了这一点:

    $ curl 'http://localhost:8080/test'
    hello
    world

这里使用多条 echo 指令是没问题的,因为它们同属 ngx_echo 模块,而且 ngx_echo 模块规定和实现了它们之间的执行顺序。值得一提的是,并非所有模块的指令都支持在同一个 location 中被使用多次,例如content_by_lua 就只能使用一次,所以下面这个例子是错误的:

    ? location /test {
    ?     content_by_lua 'ngx.say("hello")';
    ?     content_by_lua 'ngx.say("world")';
    ? }

这个配置在 Nginx 启动时就会报错:

    [emerg] "content_by_lua" directive is duplicate ...

正确的写法应当是:

    location /test {
        content_by_lua 'ngx.say("hello") ngx.say("world")';
    }

即在 content_by_lua 内联的 Lua 代码中调用两次 ngx.say 函数,而不是在当前 location 中使用两次content_by_lua 指令。

类似地,ngx_proxy 模块的 proxy_pass 指令和 echo 指令也不能同时用在一个 location 中,因为它们也同属 content 阶段。不少 Nginx 新手都会犯类似下面这样的错误:

    ? location /test {
    ?     echo "before...";
    ?     proxy_pass http://127.0.0.1:8080/foo;
    ?     echo "after...";
    ? }
    ?
    ? location /foo {
    ?     echo "contents to be proxied";
    ? }

这个例子表面上是想在 ngx_proxy 模块返回的内容前后,通过 ngx_echo 模块的 echo 指令分别输出字符串"before..." 和 "after...",但其实只有其中一个模块能在 content 阶段运行。测试结果表明,在这个例子中是 ngx_proxy 模块胜出,而 ngx_echo 模块的 echo 指令根本没有运行:

    $ curl 'http://localhost:8080/test'
    contents to be proxied

要实现这个例子希望达到的效果,需要改用 ngx_echo 模块提供的 echo_before_body 和 echo_after_body 这两条配置指令:

    location /test {
        echo_before_body "before...";
        proxy_pass http://127.0.0.1:8080/foo;
        echo_after_body "after...";
    }
 
    location /foo {
        echo "contents to be proxied";
    }

测试结果表明这一次我们成功了:

    $ curl 'http://localhost:8080/test'
    before...
    contents to be proxied
    after...

配置指令 echo_before_body 和 echo_after_body 之所以可以和其他模块运行在 content 阶段的指令一起工作,是因为它们运行在 Nginx 的“输出过滤器”中。前面我们在 (一) 中分析 echo 指令产生的“调试日志”时已经知道,Nginx 在输出响应体数据时都会调用“输出过滤器”,所以 ngx_echo 模块才有机会在“输出过滤器”中对 ngx_proxy 模块产生的响应体输出进行修改(即在首尾添加新的内容)。值得一提的是,“输出过滤器”并不属于 (一) 中提到的那 11 个请求处理阶段(毕竟许多阶段都可以通过输出响应体数据来调用“输出过滤器”),但这并不妨碍 echo_before_body 和 echo_after_body 指令在文档中标记下面这一行:

    phase: output filter

这一行的意思是,当前配置指令运行在“输出过滤器”这个特殊的阶段。

Nginx 配置指令的执行顺序(五)的更多相关文章

  1. Nginx 配置指令的执行顺序(八)

    前面我们详细讨论了 rewrite.access 和 content 这三个最为常见的 Nginx 请求处理阶段,在此过程中,也顺便介绍了运行在这三个阶段的众多 Nginx 模块及其配置指令.同时可以 ...

  2. Nginx 配置指令的执行顺序(一)

    大多数 Nginx 新手都会频繁遇到这样一个困惑,那就是当同一个 location 配置块使用了多个 Nginx 模块的配置指令时,这些指令的执行顺序很可能会跟它们的书写顺序大相径庭.于是许多人选择了 ...

  3. Nginx配置指令的执行顺序

    rewrite阶段 rewrite阶段是一个比较早的请求处理阶段,这个阶段的配置指令一般用来对当前请求进行各种修改(比如对URI和URL参数进行改写),或者创建并初始化一系列后续处理阶段可能需要的Ng ...

  4. Nginx 配置指令的执行顺序(六)

    前面我们在 (五) 中提到,在一个 location 中使用 content 阶段指令时,通常情况下就是对应的 Nginx 模块注册该 location 中的“内容处理程序”.那么当一个 locati ...

  5. Nginx 配置指令的执行顺序(三)

    如前文所述,除非像 ngx_set_misc 模块那样使用特殊技术,其他模块的配置指令即使是在 rewrite 阶段运行,也不能和 ngx_rewrite 模块的指令混合使用.不妨来看几个这样的例子. ...

  6. Nginx 配置指令的执行顺序(十)

    运行在 post-rewrite 阶段之后的是所谓的 preaccess 阶段.该阶段在 access 阶段之前执行,故名preaccess. 标准模块 ngx_limit_req 和 ngx_lim ...

  7. Nginx 配置指令的执行顺序(二)

    我们前面已经知道,当 set 指令用在 location 配置块中时,都是在当前请求的 rewrite 阶段运行的.事实上,在此上下文中,ngx_rewrite 模块中的几乎全部指令,都运行在 rew ...

  8. Nginx 配置指令的执行顺序

    在一个 location 中使用 content 阶段指令时,通常情况下就是对应的 Nginx 模块注册该 location 中的“内容处理程序”.那么当一个 location 中未使用任何 cont ...

  9. Nginx 配置指令的执行顺序(十一)

    紧跟在 post-access 阶段之后的是 try-files 阶段.这个阶段专门用于实现标准配置指令 try_files 的功能,并不支持 Nginx 模块注册处理程序.由于 try_files  ...

随机推荐

  1. 编译安装hph

    一.安装相关的依赖: yum -y install gcc gcc-c++ bzip2-devel libjpeg-devel libpng-devel freetype-devel libxml2- ...

  2. [TYVJ] P1065 津津的储蓄计划

    津津的储蓄计划 背景 Background NOIP2004 提高组 第一道   描述 Description     津津的零花钱一直都是自己管理.每个月的月初妈妈给津津300元钱,津津会预算这个月 ...

  3. SQL Server 2005中的分区表(五):添加一个分区

    所谓天下大事,分久必合,合久必分,对于分区表而言也一样.前面我们介绍过如何删除(合并)分区表中的一个分区,下面我们介绍一下如何为分区表添加一个分区. 为分区表添加一个分区,这种情况是时常会 发生的.比 ...

  4. Spring MVC对象转换说明

    在Spring MVC之前我们需要在Servlet里处理HttpServletRequest参数对象,但这个对象里的属性都是通用类型的对象(如字符串),处理起来很繁琐并且容易出错,而Spring MV ...

  5. 【CF 675D Tree Construction】BST

    题目链接:http://codeforces.com/problemset/problem/675/D 题意:给一个由n个互异整数组成的序列a[],模拟BST的插入过程,依次输出每插入一个元素a[i] ...

  6. Skulpt

    Skulpt Python. Client side. Skulpt is an entirely in-browser implementation of Python. No preprocess ...

  7. PHP 9 大缓存技术总结

    1.全页面静态化缓存 也就是将页面全部生成html静态页面,用户访问时直接访问的静态页面,而不会去走php服务器解析的流程.此种方式,在CMS系统中比较常见,比如dedecms: 一种比较常用的实现方 ...

  8. JFreeChart多坐标轴曲线图

    jar包:jcommon-1.0.23.jarjfreechart-1.0.19.jar maven配置: <dependency> <groupId>jfree</gr ...

  9. Stones(优先队列)

    Stones Time Limit: 5000/3000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Total Subm ...

  10. 【筛素数表证明】【O[n]】

    void get_prime() { int cnt = 0; for (int i = 2; i < N; i++) { if (!tag[i]) p[cnt++] = i; for (int ...