Nginx upstream原理分析【1】upstream和FastCGI前篇
在前一篇文章“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个地方:
- r->header_in;如果在读取请求头的时候已经读入了一部分数据,则放入这里
- 没有预读的数据部分新分配一块内存存放。
上面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函数了,它用来发送客户端数据;限于篇幅,这部分下一篇文章介绍。电脑没电了
近期评论