事由

我们基于Vanilla开发了一个类似于一个网关的流量分发服务,在原来的业务线上对不同的业务使用不同的后端(PHP、Python、Lua...)进行处理,最近在紧锣密鼓的测试(当然这里咱们主要看问题),在扫荡日志的过程中发现有这样的一条 [error] (日志已打码)

2016/03/01 16:35:36 [error] 32462#0: *1 attempt to set ngx.status after sending out response headers while sending to client, client: xx.xx.xx.xx, server: x.sina.cn, request: "GET /xxx HTTP/1.1", host: "xx.sina.cn:9110"

没错,就是条: 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日志。

2016/03/01 16:35:36 [debug] 32462#0: *1 posix_memalign: 0000000000E8DA10:4096 @16
2016/03/01 16:35:36 [debug] 32462#0: *1 HTTP/1.1 200 OK
Server: openresty/1.9.3.1
Date: Tue, 01 Mar 2016 08:35:36 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Power-By: Vanilla-0.1.0-rc4
Set-Cookie: xx=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; domain=.sina.cn
Set-Cookie: xx=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; domain=.sina.cn
cache-status: MISS
X-Powered-By: PHP/7.0.02016/03/01 16:35:36 [debug] 32462#0: *1 write new buf t:1 f:0 0000000000E8DA80, pos 0000000000E8DA80, size: 460 file: 0, size: 0
2016/03/01 16:35:36 [debug] 32462#0: *1 http write filter: l:0 f:0 s:460
2016/03/01 16:35:36 [debug] 32462#0: *1 lua sending last buf of the response body
2016/03/01 16:35:36 [debug] 32462#0: *1 http output filter "/xxx"
2016/03/01 16:35:36 [debug] 32462#0: *1 http copy filter: "/xxx"
2016/03/01 16:35:36 [debug] 32462#0: *1 lua capture body filter, uri "/xxx"
2016/03/01 16:35:36 [debug] 32462#0: *1 http postpone filter "/xxx" 00007FFFE0ECB970
2016/03/01 16:35:36 [debug] 32462#0: *1 http chunk: 0
2016/03/01 16:35:36 [debug] 32462#0: *1 write old buf t:1 f:0 0000000000E8DA80, pos 0000000000E8DA80, size: 460 file: 0, size: 0
2016/03/01 16:35:36 [debug] 32462#0: *1 write new buf t:0 f:0 0000000000000000, pos 00000000004F811A, size: 5 file: 0, size: 0
2016/03/01 16:35:36 [debug] 32462#0: *1 http write filter: l:1 f:0 s:465
2016/03/01 16:35:36 [debug] 32462#0: *1 http write filter limit 0
2016/03/01 16:35:36 [debug] 32462#0: *1 writev: 465 of 465
2016/03/01 16:35:36 [debug] 32462#0: *1 http write filter 0000000000000000
2016/03/01 16:35:36 [debug] 32462#0: *1 http copy filter: 0 "/xxx"
2016/03/01 16:35:36 [error] 32462#0: *1 attempt to set ngx.status after sending out response headers while sending to client, client: xx.xx.xx.xx, server: x.sina.cn, request: "GET /xxx HTTP/1.1", host: "xx.sina.cn:9110"

一看果然是在响应发出后报的错,但日志没有反应出报错的具体位置。没办法,我只能通过“二分步进法”,打一堆日志来跟进,人肉找出来到底什么地方报的错。

ngx_log(ngx.ERR, "-------------=====1=======--------------------->")
ngx.exit(ngx.ERROR)

最后跟到这样一处逻辑:

if response:response() then ngx.eof() end

请求正常完成后,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_moduleproxy_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的文档是我见过开源项目中写的比较好的,虽然是英文。还是值得仔细研习。

由一条OpenResty Error log谈谈ngx.exit与ngx.eof的区别相关推荐

  1. ​清除error.log、access.log并限制Apache日志文件大小的方法

    有个客户反映vps网站打不开,登录系统检查下,客户是win2003系统,发现放置网站文件的D盘已被占满,可用空间为0KB,进去发现客户Apache服务器的错误日志error.log非常的大,造成D盘被 ...

  2. 清除error.log、access.log并限制Apache日志文件大小的方法

    清除error.log.access.log并限制Apache日志文件大小的方法 Apache下的access.log和error.log文件从安装服务器到现在没有动过,今天突然discuz 的MYS ...

  3. linux apache 大文件,Apache下error.log文件太大的处理方法

    Apache下error.log文件太大的处理方法 2019年05月03日 | 萬仟网网络运营 | 我要评论 清除error.log.access.log并限制apache日志文件大小的方法 有个客户 ...

  4. php日志文件过大,Windows服务器系统下Apache的error.log文件过大的解决办法

    今天打开我的电脑的时候,突然发现硬盘空间少了很多,仔细检查各个文件夹大小后发现,Apache文件夹的log目录占用了将近7个G的空间,把文件夹下的error.log删除重启Apache发现,error ...

  5. openresty查看log

    log的位置 log的位置是可以自定义的,比如你的启动方式如下 openresty -p /opt/apps -c conf/main-nginx.conf 那么log的位置就在main-nginx. ...

  6. web test LoadRunner error list / error log

    http://bbs.51testing.com/thread-8644-1-1.html sckOutOfMemory 7 内存不足 sckInvalidPropertyValue 380 属性值不 ...

  7. linux配置apache文件大小,linux下限制Apache日志access.log以及error.log文件大小

    如果网站开启了apache日志文件以及错误日志功能,那么很可能apache日志文件access.log在一段时间内就会很庞大至上百兆甚至几十个G,尤其大访问量的网站更是如此.error_log错误日志 ...

  8. R语言可视化分面图、假设检验分组t检验、可视化单变量分组分面箱图(faceting bar plot)、添加误差条(error bar)、添加p值、添加抖动数据点(jitter points)

    R语言可视化分面图.假设检验分组t检验.可视化单变量分组分面箱图(faceting bar plot).添加误差条(error bar).添加p值.添加抖动数据点(jitter points) 目录

  9. R语言使用ggpubr包可视化分组条形图(grouped bar plot)、并添加误差条(error bar、误差条显示平均值+/-sd)、添加分组填充色、添加jitter、自定义分组抖动数据点形状

    R语言使用ggpubr包的ggbarplot函数可视化分组条形图(grouped bar plot).并添加误差条(error bar.误差条显示平均值+/-sd).添加分组填充色.添加jitter抖 ...

最新文章

  1. NSJSONSerliazition文档翻译和使用
  2. 字符串操作与正则表达式
  3. U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
  4. 【android】og
  5. python加载项向导_Python安装与环境变量的配置,Python安装向导
  6. 一天一个小技巧(5)——CSDN编译器的图片位置、大小调节
  7. 定时任务corn表达式设置
  8. PG 数据库表备份方案
  9. 猴子摘香蕉问题python_用Basic STRIPS算法求解猴子和香蕉
  10. Mach-O文件, 架构包framework的合并和拆分
  11. 如何在多个视频画面的任意位置上添加上同一张图片
  12. matlab二元多项式求值,matlab多项式代入求值
  13. 雷军微博拧螺丝CFO为粉丝数发愁
  14. Python命名方式转换——帕斯卡、驼峰法、下划线
  15. 【自学Python】Python布尔型(bool)
  16. 悼念 Benoit MandelBrot
  17. 观察DNF的伤害计算公式
  18. 140.210.28.29扬州BGP高防服务器
  19. Java中如何实现QR(二维码)的生成
  20. IBM Lenovo V7000存储服务器维修记录

热门文章

  1. Spring MVC访问页面拦截js和css文件解决方法( No mapping found for HTTP request with URI [/Recruit/js/popper1.15.0.)
  2. eclipse连接数据库
  3. python3.7读取csv文件_Python3 读取csv文件
  4. 二度整理交换机有感(续)
  5. 谷歌发布轻量级视觉架构MobileNetV2,速度快准确率高
  6. 全球最大地标识别数据集问世:包含200万张图片和3万处地标
  7. react离开页面,自定义弹框拦截,路由拦截
  8. 构建dubbo分布式平台-window安装zookeeper注册中心
  9. expect脚本同步文件,expect脚本指定host和要同步的文件,构建文件分发系统,批量远程执行命令...
  10. 让整个网站变成灰色的做法