首页 > Nginx > Nginx upstream原理分析【1】upstream和FastCGI前篇

Nginx upstream原理分析【1】upstream和FastCGI前篇

2013年5月29日 发表评论 阅读评论 9844次阅读    

在前一篇文章“Nginx upstream原理分析【1】新连接的处理过程”中我们介绍了一个连接从accept到ngx_http_core_run_phases过程处理所发生的事情,后面剩下的就是FCGI的相关处理了,留在这里进行介绍。

0、写在前面的话

ngx_http_core_run_phases函数会不断调用cmcf->phase_engine.handlers上面的checker,从而调用rewrite模块,find_config模块等的函数进行相应的处理,对于我们今天要介绍的FCGI模块的处理,其checker调用的是ngx_http_core_content_phase,后者调用了ngx_http_fastcgi_handler进行相关的处理。

这个ngx_http_fastcgi_handler是在nginx 解析配置的时候,解析到了ngx_string("fastcgi_pass"),指令的时候会调用ngx_http_fastcgi_pass()进行指令解析,里面就设置了clcf->handler = ngx_http_fastcgi_handler;,也就是设置到了location conf的handler上面;

当nginx进行rewrite,配置更新的时候,会调用到ngx_http_update_location_config函数,去更新各个成员的配置,从而将刚才的handler设置到了请求的r->content_handler 句柄上面,在调用ngx_http_core_content_phase里面,就会处理这个content_handler。下面看一下代码,了解一下ngx_http_fastcgi_handler的来源:

ngx_int_t
ngx_http_core_content_phase(ngx_http_request_t *r,   ngx_http_phase_handler_t *ph)
{
    size_t     root;
    ngx_int_t  rc;
    ngx_str_t  path;

    if (r->content_handler) {
//如果有content_handler,就直接调用就行了.比如如果是FCGI,在遇到配置fastcgi_pass   127.0.0.1:8777;的时候
//会调用ngx_http_fastcgi_pass函数,注册本location的处理hander为ngx_http_fastcgi_handler。
//从而在ngx_http_update_location_config里面会更新content_handler指针为当前loc所对应的指针。
        r->write_event_handler = ngx_http_request_empty_handler;
        ngx_http_finalize_request(r, r->content_handler(r));
        return NGX_OK;
    }
//······
}

static char *
ngx_http_fastcgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{//nginx 解析到fastcgi_pass指令的时候调用这里.比如fastcgi_pass   127.0.0.1:8777;
    ngx_http_fastcgi_loc_conf_t *flcf = conf;
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
	//设置句柄,会在ngx_http_update_location_config里面设置为content_handle的,从而在content phase中被调用
    clcf->handler = ngx_http_fastcgi_handler;
//`````
}
void ngx_http_update_location_config(ngx_http_request_t *r)
{//更新各种配置。根据loc_conf的配置,将对应的所需数据设置到r请求结构体上面去。
//主要的还有 r->content_handler = clcf->handler;,设置回调。
    if (clcf->handler) {//如果配置模块设置了其handler,那么将其设置到content_handler指针上面,以备待会在处理phase的时候,调用之。
//ngx_http_fastcgi_handler就在这里设置了,于是在ngx_http_core_content_phase就会调用这个FCGI函数,然后请求就交给对应的处理了
//····
//proxy 模块为ngx_http_proxy_handler。
        r->content_handler = clcf->handler;
    }
}


1、ngx_http_fastcgi_handler内容处理入口:

ngx_http_fastcgi_handler函数作为nginx读取请求的header头部后,就会调用ngx_http_core_content_phase进一步调用到这里,可以看到upstream还没有到,其实upstream是由这些fastcgi模块或者proxy模块使用的。或者说互相使用:fastcgi启动upstream,设置相关的回调,然后upstream会调用这些回调完成功能。下面看看这个函数。

  • 首先调用ngx_http_upstream_create创建一个upstream结构,设置到r->upstream上面去,以后就可以根据请求结构获取到upstream了;
  • 然后设置upstream上面的回调函数create_request,process_header,finalize_request等;
  • 再设置u->pipe->input_filter为ngx_http_fastcgi_input_filter,这个是用来解析FCGI协议数据的句柄,用在event_pipe方式接收数据,解析的,在“Nginx upstream原理分析【2】-带buffering读取upstream数据”里面会看到其用处;
  • 之后就是调用ngx_http_read_client_request_body函数,读取客户端发送过来的BODY,并且设置第二个参数为ngx_http_upstream_init,这样读取客户端BODY完成后,就好调用ngx_http_upstream_init函数去初始化upstream数据,准备发送数据到后端的FCGI,比如PHP。

关于upstream上面的几个回调函数,下面说一下主要的:

u->create_request = ngx_http_fastcgi_create_request;//这个回调主要生成发送到上游服务器的请求缓冲(或者一条缓冲链),存放在u->request_bufs链接表里面。

u->reinit_request = ngx_http_fastcgi_reinit_request;//在后端服务器被重置的情况下(在create_request被第二次调用之前)被调用

u->process_header = ngx_http_fastcgi_process_header;//处理上游服务器回复的第一个bit,时常是保存一个指向上游回复负载的指针

u->finalize_request = ngx_http_fastcgi_finalize_request;//在Nginx完成从上游服务器读入回复以后被调用

下面简单看一下代码:

static ngx_int_t
ngx_http_fastcgi_handler(ngx_http_request_t *r)
{//FCGI处理入口,ngx_http_core_run_phases里面当做一个内容处理模块调用的。
//ngx_http_core_find_config_phase里面的ngx_http_update_location_config设置
    ngx_int_t                     rc;
    ngx_http_upstream_t          *u;
    ngx_http_fastcgi_ctx_t       *f;
    ngx_http_fastcgi_loc_conf_t  *flcf;

    if (r->subrequest_in_memory) {
        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "ngx_http_fastcgi_module does not support subrequest in memory");
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (ngx_http_upstream_create(r) != NGX_OK) {//创建一个ngx_http_upstream_t结构,放到r->upstream里面去。
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    f = ngx_pcalloc(r->pool, sizeof(ngx_http_fastcgi_ctx_t));
    if (f == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    ngx_http_set_ctx(r, f, ngx_http_fastcgi_module);//r->ctx[module.ctx_index] = c;也就是将申请的fcgi_ctx_t放到这个请求的ctx里面
    flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);//得到fcgi的配置。(r)->loc_conf[module.ctx_index]
    if (flcf->fastcgi_lengths) {//如果这个fcgi有变量,那么久需要解析一下变量。
        if (ngx_http_fastcgi_eval(r, flcf) != NGX_OK) {//计算fastcgi_pass   127.0.0.1:9000;后面的URL的内容。也就是域名解析;
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
    }
    u = r->upstream;
    ngx_str_set(&u->schema, "fastcgi://");//用fcgi协议。
    u->output.tag = (ngx_buf_tag_t) &ngx_http_fastcgi_module;
    u->conf = &flcf->upstream;
#if (NGX_HTTP_CACHE)
    u->create_key = ngx_http_fastcgi_create_key;//根据flcf->cache_key里面的复杂表达式计算 scgi_cache_key line;指令后面的复杂表达式line;
#endif
    u->create_request = ngx_http_fastcgi_create_request;
    u->reinit_request = ngx_http_fastcgi_reinit_request;
    u->process_header = ngx_http_fastcgi_process_header;
    u->abort_request = ngx_http_fastcgi_abort_request;
    u->finalize_request = ngx_http_fastcgi_finalize_request;

	//下面的数据结构是给event_pipe用的,用来对FCGI的数据进行buffering处理的。FCGI写死为buffering
    u->buffering = 1;
    u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));
    if (u->pipe == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
	//设置读取fcgi协议格式数据的回调,当解析完带有\r\n\r\n的头部的FCGI包后,后面的包解析都由这个函数进行处理。
    u->pipe->input_filter = ngx_http_fastcgi_input_filter;
    u->pipe->input_ctx = r;

    rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
        return rc;
    }
    return NGX_DONE;
}

2、读取客户端请求体ngx_http_read_client_request_body:

ngx_http_read_client_request_body用来读取客户端的BODY,存放在2个地方:

  1. r->header_in;如果在读取请求头的时候已经读入了一部分数据,则放入这里
  2. 没有预读的数据部分新分配一块内存存放。

上面2种方式读取的数据都是存放在ngx_http_request_body_t结构上面的,这个结果由r->request_body = rb;填充。下面看一下这个结构:

typedef struct {
	//如果请求的POST数据需要写入文件,则此处记录文件
    ngx_temp_file_t                  *temp_file;

    //请求BODY的BUFFER链接表,如果在解析HEADER的时候预读了数据,则会分2块存放在这里
    ngx_chain_t                      *bufs;
	//指向所POST的,可以写入的缓冲区
    ngx_buf_t                        *buf;
	//r->headers_in.content_length_n - preread;计算还有多少数据需要读取,减去刚才预读的部分。不断减少
    off_t                             rest;
	//数据将要写入所指向的这个链接表的位置。
    ngx_chain_t                      *to_write;
	//POST数据读取完毕后需要调用的HANDLER函数,也就是ngx_http_upstream_init
    ngx_http_client_body_handler_pt   post_handler;
} ngx_http_request_body_t;

下面函数分2部分进行处理,也就是客户端有没有BODY。

一、没有发送body:

函数首先判断是否已经读取完了请求体,或者是否配置可以丢掉请求体discard_body,如果是就直接调用ngx_http_upstream_init去初始化upstream相关数据;
然后调用ngx_http_test_expect检查是否需要发送HTTP/1.1 100 Continue;
申请一个ngx_http_request_body_t结构用来存储客户端的请求体,并设置到r->request_body = rb;
如果content_length_n等于0,也就是客户端不会发送BODY,那OK,直接调用ngx_http_upstream_init就行了。

看看这部分的代码:

/*
 * on completion ngx_http_read_client_request_body() adds to
 * r->request_body->bufs one or two bufs:
 *    *) one memory buf that was preread in r->header_in;如果在读取请求头的时候已经读入了一部分数据,则放入这里
 *    *) one memory or file buf that contains the rest of the body 没有预读的数据部分放入这里
 */
ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_http_client_body_handler_pt post_handler)
{//post_handler = ngx_http_upstream_init。NGINX会等到请求的BODY全部读取完毕后才进行upstream的初始化,GOOD
    size_t                     preread;
    ssize_t                    size;
    ngx_buf_t                 *b;
    ngx_chain_t               *cl, **next;
    ngx_temp_file_t           *tf;
    ngx_http_request_body_t   *rb;
    ngx_http_core_loc_conf_t  *clcf;

    r->main->count++;
    if (r->request_body || r->discard_body) {//discard_body是否需要丢弃请求内容部分。或者已经有请求体了。则直接回调
        post_handler(r);//不需要请求体,直接调用ngx_http_upstream_init
        return NGX_OK;
    }

    if (ngx_http_test_expect(r) != NGX_OK) {//检查是否需要发送HTTP/1.1 100 Continue
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
    if (rb == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    r->request_body = rb;//分配请求体结构,下面进行按需填充。
    if (r->headers_in.content_length_n < 0) {
        post_handler(r);//如果不需要读取body部分,长度小于0
        return NGX_OK;
    }

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
    if (r->headers_in.content_length_n == 0) {//如果没有设置content_length_n
        if (r->request_body_in_file_only) {//client_body_in_file_only这个指令始终存储一个连接请求实体到一个文件即使它只有0字节。
            tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
            if (tf == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }
            tf->file.fd = NGX_INVALID_FILE;
            tf->file.log = r->connection->log;
            tf->path = clcf->client_body_temp_path;
            tf->pool = r->pool;
            tf->warn = "a client request body is buffered to a temporary file";
            tf->log_level = r->request_body_file_log_level;
            tf->persistent = r->request_body_in_persistent_file;
            tf->clean = r->request_body_in_clean_file;
            if (r->request_body_file_group_access) {
                tf->access = 0660;
            }
            rb->temp_file = tf;//创建一个临时文件用来存储POST过来的body。虽然这个只有0字节,啥东西都没有。
            if (ngx_create_temp_file(&tf->file, tf->path, tf->pool, tf->persistent, tf->clean, tf->access) != NGX_OK) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }
        }
	//由于实际的content_length_n长度为0,也就不需要进行读取了。直接到init
        post_handler(r);//一般GET请求直接到这里了
        return NGX_OK;
    }

二、客户端发送了body,需要读取:

后面就需要读取body了,nginx首先将ngx_http_upstream_init设置到rb->post_handler上面,以备读取完成body后调用。接着nginx根据r->header_in判断一下是否之前在读取头部数据的时候多读取了一部分BODY,如果是,就申请一块buf链表,指向这块内存并且存到rb->bufs指针上面。如果预读的数据已经足够了,也就是包含了整个body,那就可以调用ngx_http_upstream_init,否则还需要进行后面的数据读取。下面看看有预读数据的情况下,代码是如何处理的,具体看代码注释:

//好吧,这回content_length_n大于0 了,也就是个POST请求。这里先记录一下,待会POST数据读取完毕后,需要调用到这个ngx_http_upstream_init
    rb->post_handler = post_handler;

    preread = r->header_in->last - r->header_in->pos;//使用之前读入的剩余数据,如果之前预读了数据的话。
    if (preread) {//如果之前预读了多余的请求体
        /* there is the pre-read part of the request body */
        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http client request body preread %uz", preread);

        b = ngx_calloc_buf(r->pool);//分配ngx_buf_t结构,用于存储预读的数据
        if (b == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
        b->temporary = 1;
        b->start = r->header_in->pos;//直接指向已经预读的数据的开头。这个POS已经在外面就设置好了的。读取请求头,HEADERS后就移位了。
        b->pos = r->header_in->pos;
        b->last = r->header_in->last;
        b->end = r->header_in->end;

        rb->bufs = ngx_alloc_chain_link(r->pool);//申请一个buf链接表。用来存储2个BODY部分
        if (rb->bufs == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        rb->bufs->buf = b;//预读的BODY部分放在这里
        rb->bufs->next = NULL;//其余部分待会读取的时候放在这里
        rb->buf = b;//ngx_http_request_body_t 的buf指向这块新的buf

        if ((off_t) preread >= r->headers_in.content_length_n) {//OK,我已经读了足够的BODY了,可以想到,下面可以直接去ngx_http_upstream_init了
            /* the whole request body was pre-read */
            r->header_in->pos += (size_t) r->headers_in.content_length_n;
            r->request_length += r->headers_in.content_length_n;//统计请求的总长度

            if (r->request_body_in_file_only) {//如果需要记录到文件,则写入文件
                if (ngx_http_write_request_body(r, rb->bufs) != NGX_OK) {
                    return NGX_HTTP_INTERNAL_SERVER_ERROR;
                }
            }
            post_handler(r);//进行处理
            return NGX_OK;
        }
		//如果预读的数据还不够,还有部分数据没有读入进来。
        /*
         * to not consider the body as pipelined request in
         * ngx_http_set_keepalive()
         */
        r->header_in->pos = r->header_in->last;//后移,待会从这里写入,也就是追加吧
        r->request_length += preread;//统计总长度

        rb->rest = r->headers_in.content_length_n - preread;//计算还有多少数据需要读取,减去刚才预读的部分。
        if (rb->rest <= (off_t) (b->end - b->last)) {//如果还要读取的数据大小足够容纳到现在的预读BUFFER里面,那就干脆放入其中吧。
            /* the whole request body may be placed in r->header_in */
            rb->to_write = rb->bufs;//可以写入第一个位置rb->bufs->buf = b;
            r->read_event_handler = ngx_http_read_client_request_body_handler;//设置为读取客户端的请求体
            return ngx_http_do_read_client_request_body(r);//果断的去开始读取剩余数据了
        }
		//如果预读的BUFFER容不下所有的。那就需要分配一个新的了。
        next = &rb->bufs->next;//设置要读取的数据为第二个buf

    } else {
        b = NULL;//没有预读数据
        rb->rest = r->headers_in.content_length_n;//设置所需读取的数据为所有的。
        next = &rb->bufs;//然后设置要读取的数据所存放的位置为bufs的开头
    }

如果没有预读的数据,并且BODY大小部位0,或者已经有预读数据了但是还不够,并且预读数据块后面剩下的空间不多了,不够容纳下后续读入的body,那就用rb->rest 记住还有多少BODY要读取,并且next指针指向&rb->bufs->next;也就是ngx_http_request_body_t的第一块内存的next指针地址。
后面就是申请一个新的缓冲区,并且挂入next指向的指针地址,也就是上面的&rb->bufs->next或者 &rb->bufs;就是说申请一个新的缓存及诶单,放入rb->bufs的最后面。然后就调用ngx_http_do_read_client_request_body去进行数据读取,其里面也就是一个大的循环,循环里面调用c->recv(c, rb->buf->last, size);//使劲读数据。等于ngx_unix_recv,就光读数据,不改变epoll属性。

ngx_http_do_read_client_request_body采用readv的方式读取客户端发送过来的BODY,这里就不详细介绍啦,其读取剩余的POST数据,存放在r->request_body里面,如果读完了,回调post_handler,其实就是ngx_http_upstream_init。

    size = clcf->client_body_buffer_size;//配置的最大缓冲区大小。
    size += size >> 2;//设置大小为size + 1/4*size,剩余的内容不超过缓冲区大小的1.25倍,一次读完(1.25可能是经验值吧),否则,按缓冲区大小读取。

    if (rb->rest < size) {//如果剩下的比1.25倍最大缓冲区大小要小的话
        size = (ssize_t) rb->rest;//记录所需剩余读入字节数
        if (r->request_body_in_single_buf) {//如果指定只用一个buffer则要加上预读的。
            size += preread;
        }

    } else {//如果1.25倍最大缓冲区大小不足以容纳POST数据,那我们也只读取最大POST数据了。
        size = clcf->client_body_buffer_size;
        /* disable copying buffer for r->request_body_in_single_buf */
        b = NULL;
    }

    rb->buf = ngx_create_temp_buf(r->pool, size);//分配这么多临时内存
    if (rb->buf == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    cl = ngx_alloc_chain_link(r->pool);//分配一个链接表
    if (cl == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    cl->buf = rb->buf;//记录一下刚才申请的内存,待会数据就存放在这了。
    cl->next = NULL;//没有下一个了。
    if (b && r->request_body_in_single_buf) {//如果指定只用一个buffer则要加上预读的,那就需要把之前的数据拷贝过来
        size = b->last - b->pos;
        ngx_memcpy(rb->buf->pos, b->pos, size);
        rb->buf->last += size;
        next = &rb->bufs;//待会链接在头部。
    }

    *next = cl;//GOOD,链接起来。如果有预读数据,且可以放多个buffer,就链接在第二个位置,否则链接在第一个位置。
    if (r->request_body_in_file_only || r->request_body_in_single_buf) {
        rb->to_write = rb->bufs;//设置一下待会需要写入的位置。如果一个buffer,就头部

    } else {//否则如果已经设置了第二个位置,也就是有预读数据且有2份BUFFER,那就存在第二个里面,否则头部。
        rb->to_write = rb->bufs->next ? rb->bufs->next : rb->bufs;
    }
    r->read_event_handler = ngx_http_read_client_request_body_handler;//设置为读取客户端的请求体读取函数,其实就等于下面的,只是进行了超时判断

    return ngx_http_do_read_client_request_body(r);//果断的去开始读取剩余数据了
}

ngx_http_do_read_client_request_body函数如果读取完了客户端发送的BODY,那么会调用rb->post_handler(r);也就是ngx_http_upstream_init函数。


3、ngx_http_upstream_init_request请求初始化:

ngx_http_upstream_init函数判断一下超时,然后设置了个EVENT标志,然后就调用ngx_http_upstream_init_request进行实质的初始化。
该函数完成2个主要功能:

  • 1. 调用create_request创建fcgi或者proxy的数据结构。
  • 2. 调用ngx_http_upstream_connect连接下游服务器。

下面一步步看,我们这里不考虑有cache的情况。函数签名部分调用u->create_request(r),讲保存在r->request_body->bufs就是u->request_bufs 上面的数据解析为FastCGI的包,然后保存到u->request_bufs链接表里面。这个create_request指针我们再上面碰到过,对于FCGI为ngx_http_fastcgi_create_request,proxy为ngx_http_proxy_create_request函数,后者相对简单一些。

然后就是简单的设置了一下u->output的输出数据的结构,里面存有要发送的数据,以及发送的output_filter指针。

static void ngx_http_upstream_init_request(ngx_http_request_t *r)
{//ngx_http_upstream_init调用这里,此时客户端发送的数据都已经接收完毕了。
/*1. 调用create_request创建fcgi或者proxy的数据结构。
  2. 调用ngx_http_upstream_connect连接下游服务器。
  */
    ngx_str_t                      *host;
    ngx_uint_t                      i;
    ngx_resolver_ctx_t             *ctx, temp;
    ngx_http_cleanup_t             *cln;
    ngx_http_upstream_t            *u;
    ngx_http_core_loc_conf_t       *clcf;
    ngx_http_upstream_srv_conf_t   *uscf, **uscfp;
    ngx_http_upstream_main_conf_t  *umcf;

    u = r->upstream;//ngx_http_upstream_create里面设置的
    u->store = (u->conf->store || u->conf->store_lengths);
    if (!u->store && !r->post_action && !u->conf->ignore_client_abort) {//ignore_client_abort忽略客户端提前断开连接。这里指不忽略客户端提前断开。
        r->read_event_handler = ngx_http_upstream_rd_check_broken_connection;//设置回调需要检测连接是否有问题。
        r->write_event_handler = ngx_http_upstream_wr_check_broken_connection;
    }
    if (r->request_body) {//客户端发送过来的POST数据存放在此,ngx_http_read_client_request_body放的
        u->request_bufs = r->request_body->bufs;//记录客户端发送的数据,下面在create_request的时候拷贝到发送缓冲链接表里面的。
    }
	//如果是FCGI。下面组建好FCGI的各种头部,包括请求开始头,请求参数头,请求STDIN头。存放在u->request_bufs链接表里面。
	//如果是Proxy模块,ngx_http_proxy_create_request组件反向代理的头部啥的,放到u->request_bufs里面
    if (u->create_request(r) != NGX_OK) {//ngx_http_fastcgi_create_request
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }
    u->peer.local = u->conf->local;
    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    u->output.alignment = clcf->directio_alignment;
    u->output.pool = r->pool;
    u->output.bufs.num = 1;
    u->output.bufs.size = clcf->client_body_buffer_size;
	//设置过滤模块的开始过滤函数为writer。也就是output_filter。在ngx_output_chain被调用已进行数据的过滤
    u->output.output_filter = ngx_chain_writer;
    u->output.filter_ctx = &u->writer;//参考ngx_chain_writer,里面会将输出buf一个个连接到这里。

    u->writer.pool = r->pool;
    if (r->upstream_states == NULL) {//数组upstream_states,保留upstream的状态信息。
        r->upstream_states = ngx_array_create(r->pool, 1, sizeof(ngx_http_upstream_state_t));
        if (r->upstream_states == NULL) {
            ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
            return;
        }
    } else {//如果已经有了,新加一个。
        u->state = ngx_array_push(r->upstream_states);
        if (u->state == NULL) {
            ngx_http_upstream_finalize_request(r, u,  NGX_HTTP_INTERNAL_SERVER_ERROR);
            return;
        }
        ngx_memzero(u->state, sizeof(ngx_http_upstream_state_t));
    }
	//挂在清理回调函数,干嘛的暂不清楚
    cln = ngx_http_cleanup_add(r, 0);//环形链表,申请一个新的元素。
    if (cln == NULL) {
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }
    cln->handler = ngx_http_upstream_cleanup;//干嘛的
    cln->data = r;//指向所指的请求结构体。
    u->cleanup = &cln->handler;

然后进行fastcgi_pass 后面的URL的简析,解析出unix域,或者socket.这个在ngx_http_fastcgi_handler也会做这个操作的,如果解析完了,那在这里就不需要解析。这里不是重点,简单过了,具体可以参考褚霸的博客:http://www.pagefault.info/?p=251。

ngx_http_upstream_init_request创建FCGI格式的数据包后,就调用ngx_http_upstream_connect去连接后端的FCGI。


4、ngx_http_upstream_connect 连接后端FastCGI:

ngx_http_upstream_connect函数完成跟后端FCGI的连接,如果连接成功就进入发送请求阶段。设置各个回调。

调用socket,connect连接一个后端的peer,然后设置读写事件回调函数,进入发送数据的ngx_http_upstream_send_request里面。这里负责连接后端服务,然后设置各个读写事件回调。最后如果连接建立成功,会调用ngx_http_upstream_send_request进行数据发送。

函数首先调用ngx_event_connect_peer,获取一个peer,然后socket(),connect(),add_event之注册相关事件结构。下面看一下其简单的摘要代码:

ngx_int_t ngx_event_connect_peer(ngx_peer_connection_t *pc)
{//获取一个可用的peer,然后连接它.并注册可读,可写事件
    rc = pc->get(pc, pc->data);//ngx_http_upstream_get_round_robin_peer获取一个peer
    s = ngx_socket(pc->sockaddr->sa_family, SOCK_STREAM, 0);//新建一个SOCK
//在ngx_cycle->free_connections里面找一个空闲的位置存放这个连接,然后初始化相关成员
    c = ngx_get_connection(s, pc->log);
	//设置为非阻塞模式。
    if (ngx_nonblocking(s) == -1) {}
	//下面设置这个连接的读写回调函数。
    c->recv = ngx_recv;
    c->send = ngx_send;
    c->recv_chain = ngx_recv_chain;
    c->send_chain = ngx_send_chain;
    rc = connect(s, pc->sockaddr, pc->socklen);//然后连接它
    if (ngx_add_event(rev, NGX_READ_EVENT, event) != NGX_OK) {//设置读事件}
    if (ngx_add_event(wev, NGX_WRITE_EVENT, event) != NGX_OK) {//设置写事件}
}

ngx_http_upstream_connect的第一部分代码也就是跟后端建立连接的代码比较简单:

static void ngx_http_upstream_connect(ngx_http_request_t *r, ngx_http_upstream_t *u)
{//调用socket,connect连接一个后端的peer,然后设置读写事件回调函数,进入发送数据的ngx_http_upstream_send_request里面
//这里负责连接后端服务,然后设置各个读写事件回调。最后如果连接建立成功,会调用ngx_http_upstream_send_request进行数据发送。
    ngx_int_t          rc;
    ngx_time_t        *tp;
    ngx_connection_t  *c;

    r->connection->log->action = "connecting to upstream";
    r->connection->single_connection = 0;
    if (u->state && u->state->response_sec) {
        tp = ngx_timeofday();//获取缓存的时间
        u->state->response_sec = tp->sec - u->state->response_sec;//记录时间状态。
        u->state->response_msec = tp->msec - u->state->response_msec;
    }
	//更新一下状态数据
    u->state = ngx_array_push(r->upstream_states);//增加一个上游模块的状态
    if (u->state == NULL) {
        ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }
    ngx_memzero(u->state, sizeof(ngx_http_upstream_state_t));
    tp = ngx_timeofday();
    u->state->response_sec = tp->sec;
    u->state->response_msec = tp->msec;
	//下面连接后的那模块,然后设置读写回调。
    rc = ngx_event_connect_peer(&u->peer);//获取一个peer,然后socket(),connect(),add_event之注册相关事件结构
    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream connect: %i", rc);
    if (rc == NGX_ERROR) {
        ngx_http_upstream_finalize_request(r, u,  NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }
    u->state->peer = u->peer.name;
    if (rc == NGX_BUSY) {//如果这个peer被设置为忙碌状态,则尝试下一个,会递归回来的。
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no live upstreams");
        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_NOLIVE);//尝试恢复一些上游模块,然后递归调用ngx_http_upstream_connect进行连接。
        return;
    }
    if (rc == NGX_DECLINED) {
        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);//试试其他的。
        return;
    }

跟后端建立连接后,就需要设置这个连接的相关读写事件回调函数了。

    /* rc == NGX_OK || rc == NGX_AGAIN */
    c = u->peer.connection;//得到这个peer新建立的连接结构。
    c->data = r;//记住我这个连接属于哪个请求。
    c->write->handler = ngx_http_upstream_handler;//设置这个连接的读写事件结构。这是真正的读写事件回调。里面会调用write_event_handler。
    c->read->handler = ngx_http_upstream_handler;//这个是读写事件的统一回调函数,不过自己会根据读还是写调用对应的write_event_handler等

	//一个upstream的读写回调,专门做跟upstream有关的事情。上面的基本读写事件回调ngx_http_upstream_handler会调用下面的函数完成upstream对应的事情。
	u->write_event_handler = ngx_http_upstream_send_request_handler;//设置写事件的处理函数。
    u->read_event_handler = ngx_http_upstream_process_header;//读回调

    c->sendfile &= r->connection->sendfile;
    u->output.sendfile = c->sendfile;

    c->pool = r->pool;
    c->log = r->connection->log;
    c->read->log = c->log;
    c->write->log = c->log;
    /* init or reinit the ngx_output_chain() and ngx_chain_writer() contexts */
    u->writer.out = NULL;//这是i用来给ngx_chain_write函数记录发送缓冲区的。
    u->writer.last = &u->writer.out;//指向自己的头部。形成循环的结构。
    u->writer.connection = c;
    u->writer.limit = 0;

接下来,如果客户端发送了请求的BODY,并且其写入到了临时文件里面,那就将这块r->request_body->buf存放到u->output->free空闲内存列表中去以备复用,回收。
最后调用ngx_http_upstream_send_request将客户端数据发送出去。

    if (u->request_sent) {//如果是已经发送了请求。却还需要连接,得重新初始化一下
        if (ngx_http_upstream_reinit(r, u) != NGX_OK) {
            ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR);
            return;
        }
    }
    if (r->request_body //客户端发送过来的POST数据存放在此,ngx_http_read_client_request_body放的
		&& r->request_body->buf && r->request_body->temp_file && r == r->main)
    {//request_body是FCGI结构的数据。如果客户端发送了BODY,并且这个BODY存在临时文件里面,那么我们可以立即复用 r->request_body->buf了。
        /*
         * the r->request_body->buf can be reused for one request only,
         * the subrequests should allocate their own temporay bufs
         */
        u->output.free = ngx_alloc_chain_link(r->pool);
        if (u->output.free == NULL) {
            ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR);
            return;
        }//待会需要释放的东西。
        //指向请求的BODY数据部分,啥意思?把数据放到output变量里面,待会到send_request里面会拷贝到输出链表进行发送
        u->output.free->buf = r->request_body->buf;
        u->output.free->next = NULL;
        u->output.allocated = 1;
		//清空这块内存,干嘛呢?因为请求的FCGI数据已经拷贝到了ngx_http_upstream_s的request_bufs链接表里面
        r->request_body->buf->pos = r->request_body->buf->start;
        r->request_body->buf->last = r->request_body->buf->start;
        r->request_body->buf->tag = u->output.tag;
    }

    u->request_sent = 0;//还没发送请求体呢。
    if (rc == NGX_AGAIN) {//如果刚才的rc表示连接尚未建立,则设置连接超时时间。
        ngx_add_timer(c->write, u->conf->connect_timeout);
        return;
    }
#if (NGX_HTTP_SSL)
    if (u->ssl && c->ssl == NULL) {
        ngx_http_upstream_ssl_init_connection(r, u, c);
        return;
    }
#endif
    ngx_http_upstream_send_request(r, u);//已经连接成功后端,下面进行数据发送。
}

5、ngx_http_upstream_send_request发送FCGI格式数据给PHP:

下面就是ngx_http_upstream_send_request函数了,它用来发送客户端数据;限于篇幅,这部分下一篇文章介绍。电脑没电了

Share
分类: Nginx 标签: , , , ,

注意: 评论者允许使用'@user空格'的方式将自己的评论通知另外评论者。例如, ABC是本文的评论者之一,则使用'@ABC '(不包括单引号)将会自动将您的评论发送给ABC。使用'@all ',将会将评论发送给之前所有其它评论者。请务必注意user必须和评论者名相匹配(大小写一致)。