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,但是一旦进入它调用的子函数的处理流程后,一切就不是那么简单了,我们会在下面一篇博客中仔细阐述,敬请期待。

0 0
原创粉丝点击