我们基于Vanilla开拓了一个类似于一个网关的流量分发做事,在原来的业务线上对不同的业务利用不同的后端(PHP、Python、Lua...)进行处理,最近在紧锣密鼓的测试(当然这里咱们紧张看问题),在扫荡日志的过程中创造有这样的一条[error]
(日志已打码)
没错,便是条: attempt to set ngx.status after sending out response headers while sending to client,大存问思是我在相应头已经发出后又考试测验对 ngx.status 进行了修正,可是我肯定不会想那么干的,而且页面要求看着明明是正常的。
本着负责卖力的态度,我又对代码逻辑和写法前前后后梳理数次,然事实上并没有创造我试图那么干,至少本意是确定的。面对这个幽灵般的缺点,一个程序员的直觉见告我,肯定是我写了一个bug?或者我的某些逻辑触发了Vanilla的bug?或者触发了OpenResty的bug?越想越激动,我必须把它找出来。

为了避免大家稠浊各种Vanilla,这里先附上Vanilla项目地址:
Github:https://github.com/idevz/vanilla
GitOSC:http://git.oschina.net/idevz/vanilla
Debug
逻辑上肉眼没看出什么问题,只能通过debug来办理。到底哪行报出来的缺点呢?在公司开拓机上添加 --with-debug
参数重新编译了OpenResty,打开debug日志。
一看果真是在相应发出后报的错,但日志没有反应出报错的详细位置。没办法,我只能通过“二分步进法”,打一堆日志来跟进,人肉找出来到底什么地方报的错。
末了跟到这样一处逻辑:
要求正常完成后,response:response()
实行结果确定是true,问题一定出在 ngx.eof()
, 我的本意在于如果在routerShutdown阶段(Vanilla要求处理的第二个阶段)要求完成相应,则后面的几个阶段就不再实行,直接结束当前要求。查阅文档创造 ngx.eof()
只是显式指定了相应流输出结束,后面的代码逻辑会在做事端连续实行。而我期望确当前要求直接终止,不应该利用 ngx.eof()
而是 ngx.exit()
。下面我们细节来认识下这两个API。
ngx.eof() 与 ngx.exit()
虽然在OpenResty ngx-lua
模块文档中这两个API文档位置紧邻,但用法和功能方面却截然不同。
ngx.exit
用法: ngx.exit(status) 实行高下文: rewrite_by_lua, access_by_lua, content_by_lua, header_filter_by_lua, ngx.timer., balancer_by_lua, ssl_certificate_by_lua ngx.exit()
的利用相对大略些:
当传入的status >= 200(200即为ngx.HTTP_OK),ngx.exit()
会中断当前要求,并将传入的状态码(status)返回给nginx。
当传入的status == 0(0即为ngx.OK)则 ngx.exit()
会中断当前实行的phrase(ngx-lua模块处理要求的阶段,如content_by_lua),进而连续实行下面的phrase。
对付 ngx.exit()
须要进一步把稳的是参数status的利用,status可以传入ngx-lua所定义的所有的HTTP状态码常量(如:ngx.HTTP_OK、ngx.HTTP_GONE、ngx.HTTP_INTERNAL_SERVER_ERROR等)和两个ngx-lua模块内核常量(只支持NGX_OK和NGX_ERROR这两个,如果传入其他的如ngx.AGAIN等则进程hang住)。
文档中推举的 ngx.exit()
最佳实践是同 return
语句组合利用,目的在于增强要求被终止的语义(return ngx.exit(...)
)。
ngx.eof
用法: ok, err = ngx.eof() 实行高下文: rewrite_by_lua, access_by_lua, content_by_lua ngx.eof
除了前面所说的显式指定了相应流输出的结束,后面的逻辑连续在做事端实行外,还须要把稳以下几点:
当你禁用了HTTP1.1的keep-alive特性后可以通过调用 ngx.eof()
来使客户端主动断开连接,这个技巧可以用来做一些back-ground jobs 而不须要HTTP客户端等待连接(不过文档推举的back-ground jobs的处理办法是 ngx.timer.at
API,详情请看文档解释)。
当你创建子要求来要求在其他 location
配置的上游模块时,你该当配置这些上游模块来忽略客户端连接的中断,如果默认不是忽略的话。例如默认的标准 ngx_http_proxy_module
模块会在客户端断开连接后立即同时终止子要乞降主要求,以是在模块 ngx_http_proxy_module
将proxy_ignore_client_abort
设置为开启(proxy_ignore_client_abort on;
)就十分主要。
自 v0.8.3
起, ngx.eof()
实行成功返回1,失落败则返回 nil
和缺点描述信息。
实践创造 ngx.exit()
和 ngx.eof()
实质差异在于ngx.exit()
浸染在于中断当前操作,不管是ngx-lua模块要求处理确当前阶段还是全体要求,而 ngx.eof()
只是结束相应流的输出,中断HTTP连接,后面的代码逻辑还会连续在做事端实行,而且 ngx.eof()
支持运行的高下文比 ngx.exit()
少太多, ngx.eof()
有返回值, ngx.exit()
则没有,由于要求已经结束。
在bug和debug中发展
实在这是一个不大不小的bug,说它小,由于后来我在文档中对ngx.status的描述中创造这么一句 Setting ngx.status after the response header is sent out has no effect but leaving an error message in your nginx's error log file
解释,也便是试图在相应头发出后变动ngx.status会在缺点日志中记录一条 [error]
但是这个缺点对本次要求的相应没有影响;说它大,如果没有仔细查出来这个没有影响,那统统都是未知,很可能给系统埋下一个未知的坑,不知道哪天就会爆出来坑你一下,关键的一点还是对API的理解。OpenResty的文档是我见过开源项目中写的比较好的,虽然是英文。还是值得仔细研习。