NGINX输出问题解决过程

发现在编译了新写的ngx_http_nc_module时,以前的nginx_hello_module失效了,甚至连nginx的欢迎页面也进不去,打开浏览器的调试工具发现一直处于pending状态,真是让人费解。

 

经过导师的帮助,通过gdb调试时发现,在nginx的ngx_http_core_module中的ngx_http_core_run_phases函数,调用了如下方法,

 

这样看起来,就是通过浏览器连接nginx时,会把所有的第三方模块都跑一遍,想想之前nginx调试hello_module时,也是经过了很多个filter函数甩锅最后传给了write_filter,这里应该也差不多,访问特定的url时,nginx也不知道要交给哪个模块处理,所以只能一个个看。

 

这时就想到了在编写ngx_http_nc_module的handler函数时,并没有按照handler应该的方式要首先获取location配置,而且结尾直接返回NGX_OK,就导致return;返回空,当然出现pending应该还是nc module写的有问题,还需要进一步研究。

 

知道了这些继续查看gdb调试信息,在ngx_http_core_content_phase函数中,如果不是返回NGX_DECLINED,会直接继续

 

差不多就是结束请求的意思了,而碰到不符合的module返回NGX_DECLINED才会继续交个下一个handler函数处理

 

所以在handler函数中获取location配置真的是至关重要,于是在ngx_http_nc_handler中加入获取location配置的部分

 

 

因为nc module中只有nc这一条指令,对应的就是enable变量,mycf就是自己在nginx中的相关location配置,通过ngx_http_get_module_loc_conf可以获取到,所以在访问特定的url时,比如localhost/hello,因为在 location /hello中并没有配置nc指令,所以这时mycf->enable就不会等于1了,所以就会返回NGX_DECLINED交给下一个handler函数处理,当然这时访问localhost/hello 或者localhost时都没有问题了。

 

 

 

 

这些解决了之后,pending的原因到底是什么

使用了一下tcpdump,是一种Linux上的抓包软件,类似wireshark。用tcpdump监视指定主机的端口的数据包,可以使用tcpdump tcp port *** and host ***.

这里直接使用tcpdump tcp port 80(nginx监听的80端口)

然后分别访问100.100.60.199/hello和100.100.60.199/nc

发现了问题 下面两图是两者分别的截图

 

 

 

 

 

可以看到,访问100.100.60.199/nc时并没有返回 200 OK,当然也没有返回其他错误码,所以一直是阻塞着。回想nginx handler模块开发中要在获取location配置后生成合适的响应并发送响应头和响应体,大概问题就是出在了这里。

 

那就先构造一下响应头,参考hello module的相关部分,加上了下面几句

ngx_str_set(&r->headers_out.content_type, "text/html");

r->headers_out.status = NGX_HTTP_OK;

r->headers_out.content_length_n = strlen(part1) + strlen(part);

rc = ngx_http_send_header(r);

 

就是让http响应为NGX_HTTP_OK也就是200,按理说应该是可以了,因为在nc模块中并没有响应体而是直接用的write函数。

 

然而再次抓包之后还是么有返回200 OK。

 

于是,就要回到之前hello module的调试,要看看在hello module中nginx到底是怎么输出的,以及头部的处理到底是怎么样。

 

定位到hello module中同样的ngx_http_send_header(r);函数 先给ngx_http_hello_handler打断点,再给ngx_http_send_header打断点b ngx_http_hello_handler,   b ngx_http_send_header

然后输入c让nginx一直运行,再用浏览器访问100.100.60.199/hello,然后再c一次,就直接进入了ngx_http_send_header

 

接下来步骤和上次差不多不断的用n命令和s命令查看调用的函数,大致如下

 

 

 

可以看到,ngx_http_send_header最后主要是交给了ngx_http_header_filter和ngx_http_write_filter处理

 

ngx_http_header_filter负责计算响应头的总大小,并分配内存,组装响应头,并调用ngx_http_write_filter发送。Nginx中,header filter只会被调用一次,ngx_http_header_filter函数中首先会检查r->header_sent标识是否已经被设置,如果是的话,则直接返回;否则设置该标识,并发送响应头。另外如果是子请求的话,也会直接退出函数。

 

ngx_http_header_filter这个函数很长,主要就是做了上面这些事情

 

 

比如

 

 

因为我们在r->headers_out.status设置了NGX_HTTP_OK,所以会进入这个分支,可以看到,在这个分支中构造了status_line,而这里就是让其等于ngx_http_status_lines[0],而这个就是200 OK

 

 

下面还构造了很多,最后构造出来的头部大概就是这样

"HTTP/1.1 200 OK\r\nServer: nginx/1.14.2\r\nDate: Wed, 31 Jul 2019 05:45:36 GMT\r\nContent-Type: text/html\r\nContent-Length: 18\r\nConnection: keep-alive\r\n\r\n] \"GE\020\242}E=V"

 

当然,该函数把构造的头部依然使用ngx_chain_t的方式存储,并在最后交给了ngx_http_write_filter处理

 

这个函数最后进入了这个分支

 

返回了NGX_OK,这样头部的构造就完成了。这时查看tcpdump中,也并没有返回200 OK。看来发送完成还是要跟响应体一起。

 

回到之前调试的ngx_http_output_filter,我们知道它最后也是调用了ngx_http_write_filter ,不过这一次没有进入上面的分支,而是执行了

 

这个上次也说明了,来看看它调用的ngx_linux_sendfile_chain 函数

 

在这个函数中构造了一个ngx_iovec_t类型的header,这就是最后要发送的整个头部,上次也说明了nginx最后使用writev函数输出,而writev输出时的结构就是这种结构体。可以看到调用了ngx_output_chain_to_iovec函数

 

 

 

在该函数中,就是把之前构造好的ngx_chain_t类型的头部赋给ngx_iovec_t类型,同时也要将nginx hello module中输出的chain类型转换为ngx_iovec_t类型,打印了一下看了看

 

可以看到,就是把这两个chain赋给了vec的相应部分。它们的内存值是一样的。

 

回到ngx_linux_sendfile_chain 之后,执行了

 

就把整个头部发给了当前连接

 

在里面调用了writev函数,执行完这一步之后就收到了输出,并且tcpdump中也返回了200 OK。

这样看来,nc模块一直阻塞的原因就是头部构造和发送的问题,所以大概改了一下

 

重新编译之后,pending的问题终于解决了!

 

 

 

附:为了防止nginx调试时乱跳(由于优化所致),需要在./configure之后和make之前,进入objs文件夹,修改Makefile,删除其中的-O.

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值