nginx http proxy接收响应过程(一)
来源:互联网 发布:前瞻数据 编辑:程序博客网 时间:2024/06/16 15:54
本篇博客主要阐述nginx如何从上游服务器接收响应的过程,响应包括响应header和响应body,在这里我们比较关注响应body的接收过程。
因为nginx采用了全异步事件驱动模型,从上游服务器接收数据和向下游服务器发送数据也都是等待os通知相关socket fd上有事件发生后方可进行。我们这里不关心os如何通知nginx worker进程,只关心worker进程收到上游服务器的可读事件通知后如何处理。
做法说起来应该很简单,nginx worker进程收到通知会调用事先注册的处理例程,在该处理例程中无非就是做这么几件事:1. 从socket fd上读数据,当然在读数据之前我们得准备好缓冲区,还得非常仔细地处理读出错的各种异常情况;2. 读完的数据可能得做些后续处理,如要不要对数据做些过滤啊什么的;3. 后续还有很多复杂的逻辑处理,我们这列举了这么两点。后面我们会通过nginx的处理来分析其内部如何高效地完成数据接收、暂存的功能。
首先,我们从头说起,说说那个nginx与上游服务器的连接fd有读事件被触发的原始函数:ngx_http_upstream_process_upstream()。另外我们需要特别强调一点,为了实现读超时(即一定时间内还没有等到os对该连接的读事件),另外一个地方也可能会调用该函数,即该连接超时(为该读事件创建一个定时器放在红黑树上)时也会触发该函数,因此,在这个函数内必须还得判断到底是谁调用了它,如果是正常的读事件触发,那么后续需要从socket上读数据,如果真的是读超时触发的,那么就得进入错误处理逻辑了,让我们简单看看它的代码,揭开庐山真面目吧:
/* 在ngx_http_upstream_send_response中被注册为与 * 上游服务器的连接的读处理函数 * u->read_event_handler = ngx_http_upstream_process_upstream; */static voidngx_http_upstream_process_upstream(ngx_http_request_t *r, ngx_http_upstream_t *u){ ngx_connection_t *c; c = u->peer.connection; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream process upstream"); c->log->action = "reading upstream"; /* 到上游服务器的读超时定时器 * 如果读超时了还是会调用ngx_http_upstream_process_request * 只是会标记upstream_error = 1,在后续处理 * 这里不立即处理,为什么? */ if (c->read->timedout) { // 判断是否是由于超时调用的该函数 u->pipe->upstream_error = 1; ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); } else { /* * 调用ngx_event_pipe从上游服务器接收数据 */ if (ngx_event_pipe(u->pipe, 0) == NGX_ABORT) { ngx_http_upstream_finalize_request(r, u, 0); return; } } ngx_http_upstream_process_request(r);// 因为由于超时导致出错或者ngx_event_pipe()也可能出错,所以索性在这里统一处理出错情况了,在此之前只是标记一些错误状态而已}我们只关心如何从上游服务器读取数据,因此,我们只关心ngx_event_pipe()。
不得不说,这也是一个非常蛋疼的函数,因为有两者都可能会调用这个函数,因此,在理解这个函数的时候必须全面地思考,切不可断章取义。抛开这个函数内又调用了一些子函数,其实这个函数分析起来还比较easy,不信可以看看我下面的分析咯。
/* 它可能的调用者有2: * ngx_http_upstream_process_downstream * ngx_http_upstream_process_upstream * 参数二do_write表示了ngx_event_pipe到底是谁调用 * downtream调用则为1,否则是0 */ngx_int_tngx_event_pipe(ngx_event_pipe_t *p, ngx_int_t do_write){ u_int flags; ngx_int_t rc; ngx_event_t *rev, *wev; for ( ;; ) { if (do_write) { p->log->action = "sending to client"; /* 如果写客户端成功了 * 那么尽量再从上游服务器读 */ rc = ngx_event_pipe_write_to_downstream(p); if (rc == NGX_ABORT) { return NGX_ABORT; } // 如果向下游写被阻塞了,那就暂时别读吧 // 读了也没用,因为还是写不下去 if (rc == NGX_BUSY) { return NGX_OK; } } p->read = 0; p->upstream_blocked = 0; p->log->action = "reading upstream"; // 如何处理返回值为NGX_ERROR的情况 if (ngx_event_pipe_read_upstream(p) == NGX_ABORT) { return NGX_ABORT; } /* p->read:ngx_event_pipe_read_upstream()是否成功读到任何内容 * p->upstream_blocked:??? * ngx_event_pipe_read_upstream()中由于缓冲区不足 * 可能会设置p->uptream_blocked * 若没有读到任何内容 && 并没有由于缓冲区不足而阻塞 * 说明此时是真的读不到内容了, 跳出循环 */ if (!p->read && !p->upstream_blocked) { break; }/* 实现的比较巧妙,如果读到内容了 * 那么下次 循环就可以写内容到客户端了 */ do_write = 1; } /* 因为上面处理了与上游和下游服务器的读写事件 * 接下来根据情况将这些读写事件再添加到事件通知 * 队列上去 */ if (p->upstream->fd != -1) { rev = p->upstream->read; flags = (rev->eof || rev->error) ? NGX_CLOSE_EVENT : 0; if (ngx_handle_read_event(rev, flags) != NGX_OK) { return NGX_ABORT; } /* rev->ready ? * 为与上游服务器的读事件添加超时定时器 */ if (rev->active && !rev->ready) { ngx_add_timer(rev, p->read_timeout); /* * 若不满足上面的if,说明事件可能出错了 * 且如果该事件上已经添加了超时定时器 * 则需要删除该定时器 * rev->timer_set 在ngx_add_timer中被置位 */ } else if (rev->timer_set) { ngx_del_timer(rev); } } /* * 与客户端的写事件如同与上游服务器的读事件 * 处理逻辑一致 */ if (p->downstream->fd != -1 && p->downstream->data == p->output_ctx) { wev = p->downstream->write; if (ngx_handle_write_event(wev, p->send_lowat) != NGX_OK) { return NGX_ABORT; } if (!wev->delayed) { if (wev->active && !wev->ready) { ngx_add_timer(wev, p->send_timeout); } else if (wev->timer_set) { ngx_del_timer(wev); } } } return NGX_OK;}就像之前说的,这个函数的调用者可能有2:ngx_http_upstream_process_downstream & ngx_http_upstream_process_upstream,分别处理向下游写以及从上游读的逻辑,为了结构的紧凑,在ngx_event_pipe()中同时处理,这就加大了代码阅读的难度,其实这个函数的代码整体上就三大段:1. for循环负责从上游读和向,等到不能读或者向下游写,等到读不动或者写不进去了,那就跳出循环吧;2. 读完了还得将socket fd加入事件通知列表,否则下次该socket fd上再有事件也都没法通知了,除此之外,还需要为该读事件添加一个超时定时器,若干时间没读事件发生的话说明读出现超时,前面说过这种情况了;3. 写完了也得将socket fd加入至事件通知列表,否则下次该socket fd上再有写事件也都没法通知了,除此之外,还需要为该写事件添加一个超时定时器,若干时间没写事件发生的话说明写超时。
看看,是不是很easy,但是一旦进入它调用的子函数的处理流程后,一切就不是那么简单了,我们会在下面一篇博客中仔细阐述,敬请期待。
- nginx http proxy接收响应过程(一)
- 怎样接收http响应(http response)
- HTTP请求响应过程
- http-proxy处理转发请求响应
- nginx设置HTTP响应头
- nginx http proxy 正向代理 实例
- nginx http proxy模块缓冲区管理
- nginx 配置 http proxy 和fastcgi
- HTTP请求和响应过程
- HTTP请求和响应过程
- 浅谈HTTP请求响应过程
- HTTP请求和响应过程
- HTTP 请求和响应过程
- http的请求响应过程
- linux下配置squid http proxy过程
- nginx 域名跳转一例~~~(rewrite、proxy)
- nginx 域名跳转一例~~~(rewrite、proxy)
- nginx 域名跳转一例~~~(rewrite、proxy)
- CWnd::FromHandle与CWnd::FromHandlePermanent有什么区别
- Java Web开发——整体框架了解与构建
- hive安装
- 2012年蓝桥杯预选赛-微生物增殖(我有疑问)
- hdu 3790 最短路径问题
- nginx http proxy接收响应过程(一)
- 操作文件
- Zabbix2.2.2源码安装部署排错以及MySQL数据库监控实例
- BUPT OJ93 中序遍历序列
- 找出所有和为n的连续正整数序列
- 输入输出流
- 关于myeclipse10中配置tomcat7出现的问题
- zoj 3761 Easy billiards(建图+贪心+dfs)
- 【数据结构与算法】内部排序之四:归并排序和快速排序(含完整源码)