nginx&http 第四章 ngx http代理 && 转载
Nginx访问上游服务器的流程大致分以下几个阶段:启动upstream、连接上游服务器、向上游发送请求、接收上游响应(包头/包体)、结束请求。
upstream相关的两个重要数据结构ngx_http_upstream_t和ngx_http_upstream_conf_t
/* upstream有3种处理上游响应包体的方式,但HTTP模块如何告诉 upstream使用哪一种方式处理上游的响应包体呢?当请求的ngx_http_request_t结构体中 subrequest_in_memory标志位为1时,将采用第1种方式,即upstream不转发响应包体 到下游,由HTTP模块实现的input_filter方法处理包体;当subrequest_in_memory为0时, upstream会转发响应包体。当ngx_http_upstream_conf t配置结构体中的buffering标志位为1 畸,将开启更多的内存和磁盘文件用于缓存上游的响应包体,这意味上游网速更快;当buffering 为0时,将使用固定大小的缓冲区(就是上面介绍的buffer缓冲区)来转发响应包体。 注意 上述的8个回调方法中,只有create_request、process_header、finalize_request 是必须实现的,其余5个回调方法-input_filter init、input_filter、reinit_request、abort request、rewrite redirect是可选的。第12章会详细介绍如何使用这5个可选的回调方法。另 外, */ /* ngx_http_upstream_create方法创建ngx_http_upstream_t结构体,其中的成员还需要各个HTTP模块自行设置。 启动upstream机制使用ngx_http_upstream_init方法 */ //FastCGI memcached uwsgi scgi proxy模块的相关配置都放在该结构中 //ngx_http_request_t->upstream 中存取 //upstream资源回收在ngx_http_upstream_finalize_request struct ngx_http_upstream_s { //该结构中的部分成员是从upstream{}中的相关配置里面(ngx_http_upstream_conf_t)获取的 //处理读事件的回调方法,每一个阶段都有不同的read event handler //注意ngx_http_upstream_t和ngx_http_request_t都有该成员 分别在ngx_http_request_handler和ngx_http_upstream_handler中执行 //如果在读取后端包体的时候采用buffering方式,则在读取完头部行和部分包体后,会从置为ngx_http_upstream_process_upstream方式读取后端包体数据 ////buffering方式,非子请求,后端头部信息已经读取完毕了,如果后端还有包体需要发送,则本端通过ngx_http_upstream_process_upstream该方式读取 //非buffering方式,非子请求,后端头部信息已经读取完毕了,如果后端还有包体需要发送,则本端通过ngx_http_upstream_process_non_buffered_upstream读取 //如果有子请求,后端头部信息已经读取完毕了,如果后端还有包体需要发送,则本端通过ngx_http_upstream_process_body_in_memory读取 ngx_http_upstream_handler_pt read_event_handler; //ngx_http_upstream_process_header ngx_http_upstream_handler中执行 //处理写事件的回调方法,每一个阶段都有不同的write event handler //注意ngx_http_upstream_t和ngx_http_request_t都有该成员 分别在ngx_http_request_handler和ngx_http_upstream_handler中执行 ngx_http_upstream_handler_pt write_event_handler; //ngx_http_upstream_send_request_handler用户向后端发送包体时,一次发送没完完成,再次出发epoll write的时候调用 //表示主动向上游服务器发起的连接。 连接的fd保存在peer->connection里面 ngx_peer_connection_t peer;//初始赋值见ngx_http_upstream_connect->ngx_event_connect_peer(&u->peer); /* 当向下游客户端转发响应时(ngx_http_request_t结构体中的subrequest_in_memory标志住为0),如果打开了缓存且认为上游网速更快(conf 配置中的buffering标志位为1),这时会使用pipe成员来转发响应。在使用这种方式转发响应时,必须由HTTP模块在使用upstream机制前构造 pipe结构体,否则会出现严重的coredump错误 */ //实际上buffering为1才通过pipe发送包体到客户端浏览器 ngx_event_pipe_t *pipe; //ngx_http_fastcgi_handler ngx_http_proxy_handler中创建空间 /* request_bufs决定发送什么样的请求给上游服务器,在实现create_request方法时需要设置它 request_bufs以链表的方式把ngx_buf_t缓冲区链接起来,它表示所有需要发送到上游服务器的请求内容。 所以,HTTP模块实现的create_request回调方法就在于构造request_bufg链表 */ /* 这上面的fastcgi_param参数和客户端请求头key公用一个cl,客户端包体另外占用一个或者多个cl,他们通过next连接在一起,最终前部连接到u->request_bufs 所有需要发往后端的数据就在u->request_bufs中了,发送的时候从里面取出来即可,参考ngx_http_fastcgi_create_request*/ /* ngx_http_upstream_s->request_bufs的包体来源为ngx_http_upstream_init_request里面的u->request_bufs = r->request_body->bufs;然后在 ngx_http_fastcgi_create_request中会重新把发往后端的头部信息以及fastcgi_param信息填加到ngx_http_upstream_s->request_bufs中 */ //向上游发送包体u->request_bufs(ngx_http_fastcgi_create_request),接收客户端的包体在r->request_body //发往上游服务器的请求头内容放入该buf 空间分配在ngx_http_proxy_create_request ngx_http_fastcgi_create_request ngx_chain_t *request_bufs; //定义了向下游发送响应的方式 ngx_output_chain_ctx_t output; //输出数据的结构,里面存有要发送的数据,以及发送的output_filter指针 ngx_chain_writer_ctx_t writer; //参考ngx_chain_writer,里面会将输出buf一个个连接到这里。 writer赋值给了u->output.filter_ctx,见ngx_http_upstream_init_request //调用ngx_output_chain后,要发送的数据都会放在这里,然后发送,然后更新这个链表,指向剩下的还没有调用writev发送的。 //upstream访问时的所有限制性参数, /* conf成员,它用于设置upstream模块处理请求时的参数,包括连接、发送、接收的超时时间等。 事实上,HTTP反向代理模块在nginx.conf文件中提供的配置项大都是用来设置ngx_http_upstream_conf_t结构体中的成员的。 上面列出的3个超时时间(connect_timeout send_imeout read_timeout)是必须要设置的,因为它们默认为0,如果不设置将永远无法与上游服务器建立起TCP连接(因为connect timeout值为0)。 */ //使用upstream机制时的各种配置 例如fastcgi赋值在ngx_http_fastcgi_handler赋值来自于ngx_http_fastcgi_loc_conf_t->upstream ngx_http_upstream_conf_t *conf; #if (NGX_HTTP_CACHE) //proxy_pache_cache或者fastcgi_path_cache解析的时候赋值,见ngx_http_file_cache_set_slot ngx_array_t *caches; //u->caches = &ngx_http_proxy_main_conf_t->caches; #endif /* HTTP模块在实现process_header方法时,如果希望upstream直接转发响应,就需要把解析出的响应头部适配为HTTP的响应头部,同时需要 把包头中的信息设置到headers_in结构体中,这样,会把headers_in中设置的头部添加到要发送到下游客户端的响应头部headers_out中 */ ngx_http_upstream_headers_in_t headers_in; //存放从上游返回的头部信息, //通过resolved可以直接指定上游服务器地址.用于解析主机域名 创建和赋值见ngx_http_xxx_eval(例如ngx_http_fastcgi_eval ngx_http_proxy_eval) ngx_http_upstream_resolved_t *resolved; //解析出来的fastcgi_pass 127.0.0.1:9000;后面的字符串内容,可能有变量嘛。 ngx_buf_t from_client; /* buffer成员存储接收自上游服务器发来的响应内容,由于它会被复用,所以具有下列多种意义: a)在使用process_header方法解析上游响应的包头时,buffer中将会保存完整的响应包头: b)当下面的buffering成员为1,而且此时upstream是向下游转发上游的包体时,buffer没有意义; c)当buffering标志住为0时,buffer缓冲区会被用于反复地接收上游的包体,进而向下游转发; d)当upstream并不用于转发上游包体时,buffer会被用于反复接收上游的包体,HTTP模块实现的input_filter方法需要关注它 接收上游服务器响应包头的缓冲区,在不需要把响应直接转发给客户端,或者buffering标志位为0的情况下转发包体时,接收包体的缓冲 区仍然使用buffer。注意,如果没有自定义input_filter方法处理包体,将会使用buffer存储全部的包体,这时buf fer必须足够大!它的大小 由ngx_http_upstream_conf_t结构体中的buffer_size成员决定 */ //ngx_http_upstream_process_header中创建空间和赋值,通过该buf接受recv后端数据 //buf大小由xxx_buffer_size(fastcgi_buffer_size proxy_buffer_size memcached_buffer_size) //读取上游返回的数据的缓冲区,也就是proxy,FCGI返回的数据。这里面有http头部,也可能有body部分。其body部分会跟event_pipe_t的preread_bufs结构对应起来。就是预读的buf,其实是i不小心读到的。 //该buf本来是接收头部行信息的,但是也可能会把部分或者全部包体(当包体很小的时候)收到该buf中 ngx_buf_t buffer; //从上游服务器接收的内容在该buffer,发往上游的请求内容在request_bufs中 //表示来自上游服务器的响应包体的长度 proxy包体赋值在ngx_http_proxy_input_filter_init off_t length; //要发送给客户端的数据大小,还需要读取这么多进来。 /* out_bufs在两种场景下有不同的意义: ①当不需要转发包体,且使用默认的input_filter方法(也就是ngx_http_upstream_non_buffered_filter方法)处理包体时,out bufs将会指向响应包体, 事实上,out bufs链表中会产生多个ngx_buf_t缓冲区,每个缓冲区都指向buffer缓存中的一部分,而这里的一部分就是每次调用recv方法接收到的一段TCP流。 ②当需要转发响应包体到下游时(buffering标志位为O,即以下游网速优先),这个链表指向上一次向下游转发响应到现在这段时间内接收自上游的缓存响应 */ ngx_chain_t *out_bufs; /* 当需要转发响应包体到下游时(buffering标志位为o,即以下游网速优先),它表示上一次向下游转发响应时没有发送完的内容 */ ngx_chain_t *busy_bufs;//调用了ngx_http_output_filter,并将out_bufs的链表数据移动到这里,待发送完毕后,会移动到free_bufs /* 这个链表将用于回收out_bufs中已经发送给下游的ngx_buf_t结构体,这同样应用在buffering标志位为0即以下游网速优先的场景 */ ngx_chain_t *free_bufs;//空闲的缓冲区。可以分配 /* input_filter init与input_filter回调方法 input_filter_init与input_filter这两个方法都用于处理上游的响应包体,因为处理包体 前HTTP模块可能需要做一些初始化工作。例如,分配一些内存用于存放解析的中间状态 等,这时upstream就提供了input_filter_init方法。而input_filter方法就是实际处理包体的 方法。这两个回调方法都可以选择不予实现,这是因为当这两个方法不实现时,upstream 模块会自动设置它们为预置方法(上文讲过,由于upstream有3种处理包体的方式,所以 upstream模块准备了3对input_filter_init、input_filter方法)。因此,一旦试图重定义mput_ filter init、input_filter方法,就意味着我们对upstream模块的默认实现是不满意的,所以才 要重定义该功能。 在多数情况下,会在以下场景决定重新实现input_filter方法。 (1)在转发上游响应到下游的同时,需要做一些特殊处理 例如,ngx_http_memcached_ module模块会将实际由memcached实现的上游服务器返回 的响应包体,转发到下游的HTTP客户端上。在上述过程中,该模块通过重定义了的input_ filter方法来检测memcached协议下包体的结束,而不是完全、纯粹地透传TCP流。 (2)当无须在上、下游间转发响应时,并不想等待接收完全部的上游响应后才开始处理 请求 在不转发响应时,通常会将响应包体存放在内存中解析,如果试图接收到完整的响应后 再来解析,由于响应可能会非常大,这会占用大量内存。而重定义了input_filter方法后,可 以每解析完一部分包体,就释放一些内存。 重定义input_filter方法必须符合一些规则,如怎样取到刚接收到的包体以及如何稃放缓 冲区使得固定大小的内存缓冲区可以重复使用等。注意,本章的例子并不涉及input_filter方 法,读者可以在第12章中找到input_filter方法的使用方式。 */ //处理包体前的初始化方法,其中data参数用于传递用户数据结构,它实际上就是下面的input_filter_ctx指针 //ngx_http_XXX_input_filter_init(如ngx_http_fastcgi_input_filter_init ngx_http_proxy_input_filter_init ngx_http_proxy_input_filter_init) ngx_int_t (*input_filter_init)(void *data); /* 处理包体的方法,其中data参数用于传递用户数据结构,它实际上就是下面的input_filter_ctx指针,而bytes表示本次接收到的包体长度。 返回NGX ERROR时表示处理包体错误,请求需要结束,否则都将继续upstream流程 用来读取后端的数据,非buffering模式。ngx_http_upstream_non_buffered_filter,ngx_http_memcached_filter等。这个函数的调用时机: ngx_http_upstream_process_non_buffered_upstream等调用ngx_unix_recv接收到upstream返回的数据后就调用这里进行协议转换,不过目前转换不多。 */ //buffering后端响应包体使用ngx_event_pipe_t->input_filter 非buffering方式响应后端包体使用ngx_http_upstream_s->input_filter ,在ngx_http_upstream_send_response分叉 ngx_int_t (*input_filter)(void *data, ssize_t bytes); //ngx_http_xxx_non_buffered_filter(如ngx_http_fastcgi_non_buffered_filter ngx_http_proxy_non_buffered_copy_filter) //用于传递HTTP模块自定义的数据结构,在input_filter_init和input_filter方法被回调时会作为参数传递过去 void *input_filter_ctx;//指向所属的请求等上下文 #if (NGX_HTTP_CACHE) /* Ngx_http_fastcgi_module.c (src\http\modules): u->create_key = ngx_http_fastcgi_create_key; Ngx_http_proxy_module.c (src\http\modules): u->create_key = ngx_http_proxy_create_key; */ //ngx_http_upstream_cache中执行 ngx_int_t (*create_key)(ngx_http_request_t *r); #endif //构造发往上游服务器的请求内容 /* create_request回调方法 create_request的回调场景最简单,即它只可能被调用1次(如果不启用upstream的 失败重试机制的话): 1)在Nginx主循环(这里的主循环是指ngx_worker_process_cycle方法)中,会定期地调用事件模块,以检查是否有网络事件发生。 2)事件模块在接收到HTTP请求后会调用HTIP框架来处理。假设接收、解析完HTTP头部后发现应该由mytest模块处理,这时会调用mytest模 块的ngx_http_mytest_handler来处理。 4)调用ngx_http_up stream_init方法启动upstream。 5) upstream模块会去检查文件缓存,如果缓存中已经有合适的响应包,则会直接返回缓存(当然必须是在使用反向代理文件缓存的前提下)。 为了让读者方便地理解upstream机制,本章将不再提及文件缓存。 6)回调mytest模块已经实现的create_request回调方法。 7) mytest模块通过设置r->upstream->request_bufs已经决定好发送什么样的请求到上游服务器。 8) upstream模块将会检查resolved成员,如果有resolved成员的话,就根据它设置好上游服务器的地址r->upstream->peer成员。 9)用无阻塞的TCP套接字建立连接。 10)无论连接是否建立成功,负责建立连接的connect方法都会立刻返回。 II) ngx_http_upstreamL init返回。 12) mytest模块的ngx_http_mytest_handler方法返回NGX DONE。 13)当事件模块处理完这批网络事件后,将控制权交还给Nginx主循环。 */ //这里定义的mytest_upstream_create_request方法用于创建发送给上游服务器的HTTP请求,upstream模块将会回调它 //在ngx_http_upstream_init_request中执行 HTTP模块实现的执行create_request方法用于构造发往上游服务器的请求 //ngx_http_xxx_create_request(例如ngx_http_fastcgi_create_request) ngx_int_t (*create_request)(ngx_http_request_t *r);//生成发送到上游服务器的请求缓冲(或者一条缓冲链) /* reinit_request可能会被多次回调。它被调用的原因只有一个,就是在第一次试图向上游服务器建立连接时,如果连接由于各种异常原因失败, 那么会根据upstream中conf参数的策略要求再次重连上游服务器,而这时就会调用reinit_request方法了。图5-4描述了典型的reinit_request调用场景。 下面简单地介绍一下图5-4中列出的步骤。 1) Nginx主循环中会定期地调用事件模块,检查是否有网络事件发生。 2)事件模块在确定与上游服务器的TCP连接建立成功后,会回调upstream模块的相关方法处理。 3) upstream棋块这时会把r->upstream->request_sent标志位置为l,表示连接已经建立成功了,现在开始向上游服务器发送请求内容。 4)发送请求到上游服务器。 5)发送方法当然是无阻塞的(使用了无阻塞的套接字),会立刻返回。 6) upstream模块处理第2步中的TCP连接建立成功事件。 7)事件模块处理完本轮网络事件后,将控制权交还给Nginx主循环。 8) Nginx主循环重复第1步,调用事件模块检查网络事件。 9)这时,如果发现与上游服务器建立的TCP连接已经异常断开,那么事件模块会通知upstream模块处理它。 10)在符合重试次数的前提下,upstream模块会毫不犹豫地再次用无阻塞的套接字试图建立连接。 11)无论连接是否建立成功都立刻返回。 12)这时检查r->upstream->request_sent标志位,会发现它已经被置为1了。 13)如果mytest模块没有实现reinit_request方法,那么是不会调用它的。而如果reinit_request不为NULL空指针,就会回调它。 14) mytest模块在reinit_request中处理完自己的事情。 15)处理宪第9步中的TCP连接断开事件,将控制权交还给事件模块。 16)事件模块处理完本轮网络事件后,交还控制权给Nginx主循环。 */ //与上游服务器的通信失败后,如果按照重试规则还需要再次向上游服务器发起连接,则会调用reinit_request方法 //下面的upstream回调指针是各个模块设置的,比如ngx_http_fastcgi_handler里面设置了fcgi的相关回调函数。 //ngx_http_XXX_reinit_request(ngx_http_fastcgi_reinit_request) //在ngx_http_upstream_reinit中执行 ngx_int_t (*reinit_request)(ngx_http_request_t *r);//在后端服务器被重置的情况下(在create_request被第二次调用之前)被调用 /* 收到上游服务器的响应后就会回调process_header方法。如果process_header返回NGXAGAIN,那么是在告诉upstream还没有收到完整的响应包头, 此时,对子本次upstream请求来说,再次接收到上游服务器发来的TCP流时,还会调用process_header方法处理,直到process_header函数返回 非NGXAGAIN值这一阶段才会停止 process_header回调方法process_header是用于解析上游服务器返回的基于TCP的响应头部的,因此,process_header可能会被多次调用, 它的调用次数与process_header的返回值有关。如图5-5所示,如果process_header返回NGX_AGAIN,这意味着还没有接收到完整的响应头部, 如果再次接收到上游服务器发来的TCP流,还会把它当做头部,仍然调用process_header处理。而在图5-6中,如果process_header返回NGX_OK (或者其他非NGX_AGAIN的值),那么在这次连接的后续处理中将不会再次调用process_header。 process header回调场景的序列图 下面简单地介绍一下图5-5中列出的步骤。 1) Nginx主循环中会定期地调用事件模块,检查是否有网络事件发生。 2)事件模块接收到上游服务器发来的响应时,会回调upstream模块处理。 3) upstream模块这时可以从套接字缓冲区中读取到来自上游的TCP流。 4)读取的响应会存放到r->upstream->buffer指向的内存中。注意:在未解析完响应头部前,若多次接收到字符流,所有接收自上游的 响应都会完整地存放到r->upstream->buffer缓冲区中。因此,在解析上游响应包头时,如果buffer缓冲区全满却还没有解析到完整的响应 头部(也就是说,process_header -直在返回NGX_AGAIN),那么请求就会出错。 5)调用mytest模块实现的process_header方法。 6) process_header方法实际上就是在解析r->upstream->buffer缓冲区,试图从中取到完整的响应头部(当然,如果上游服务器与Nginx通过HTTP通信, 就是接收到完整的HTTP头部)。 7)如果process_header返回NGX AGAIN,那么表示还没有解析到完整的响应头部,下次还会调用process_header处理接收到的上游响应。 8)调用元阻塞的读取套接字接口。 9)这时有可能返回套接字缓冲区已经为空。 10)当第2步中的读取上游响应事件处理完毕后,控制权交还给事件模块。 11)事件模块处理完本轮网络事件后,交还控制权给Nginx主循环。 */ //ngx_http_upstream_process_header和ngx_http_upstream_cache_send函数中调用 /* 解析上游服务器返回响应的包头,返回NGX_AGAIN表示包头还没有接收完整,返回NGX_HTTP_UPSTREAM_INVALID_HEADER表示包头不合法,返回 NGX ERROR表示出现错误,返回NGX_OK表示解析到完整的包头 */ //ngx_http_fastcgi_process_header ngx_http_proxy_process_status_line->ngx_http_proxy_process_status_line(ngx_http_XXX_process_header) //在ngx_http_upstream_process_header中执行 ngx_int_t (*process_header)(ngx_http_request_t *r); //处理上游服务器回复的第一个bit,时常是保存一个指向上游回复负载的指针 void (*abort_request)(ngx_http_request_t *r);//在客户端放弃请求的时候被调用 ngx_http_XXX_abort_request /* 当调用ngx_http_upstream_init启动upstream机制后,在各种原因(无论成功还是失败)导致该请求被销毁前都会调用finalize_request方 法。在finalize_request方法中可以不做任何事情,但必须实现finalize_request方法,否则Nginx会出现空指针调用的严重错误。 当请求结束时,将会回调finalize_request方法,如果我们希望此时释放资源,如打开 的句柄等,.那么可以把这样的代码添加到finalize_request方法中。本例中定义了mytest_ upstream_finalize_request方法,由于我们没有任何需要释放的资源,所以该方法没有完成任 何实际工作,只是因为upstream模块要求必须实现finalize_request回调方法 */ //销毁upstream请求时调用 ngx_http_XXX_finalize_request //在ngx_http_upstream_finalize_request中执行 ngx_http_fastcgi_finalize_request void (*finalize_request)(ngx_http_request_t *r, ngx_int_t rc); //请求结束时会调用 //在Nginx完成从上游服务器读入回复以后被调用 /* 在重定向URL阶段,如果实现了rewrite_redirect回调方法,那么这时会调用rewrite_redirect。 可以查看upstream模块的ngx_http_upstream_rewrite_location方法。如果upstream模块接收到完整的上游响应头部, 而且由HTTP模块的process_header回调方法将解析出的对应于Location的头部设置到了ngx_http_upstream_t中的headers in成员时, ngx_http_upstream_process_headers方法将会最终调用rewrite_redirect方法 因此,rewrite_ redirect的使用场景比较少,它主要应用于HTTP反向代理模蛱(ngx_http_proxy_module)。 赋值为ngx_http_proxy_rewrite_redirect */ //在上游返回的响应出现Location或者Refresh头部表示重定向时,会通迂ngx_http_upstream_process_headers方法调用到可由HTTP模块实现的rewrite redirect方法 ngx_int_t (*rewrite_redirect)(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix);//ngx_http_upstream_rewrite_location中执行 ngx_int_t (*rewrite_cookie)(ngx_http_request_t *r, ngx_table_elt_t *h); ngx_msec_t timeout; //用于表示上游响应的错误码、包体长度等信息 ngx_http_upstream_state_t *state; //从r->upstream_states分配获取,见ngx_http_upstream_init_request //当使用cache的时候,ngx_http_upstream_cache中设置 ngx_str_t method; //不使用文件缓存时没有意义 //GET,HEAD,POST //schema和uri成员仅在记录日志时会用到,除此以外没有意义 ngx_str_t schema; //就是前面的http,https,mecached:// fastcgt://(ngx_http_fastcgi_handler)等。 ngx_str_t uri; #if (NGX_HTTP_SSL) ngx_str_t ssl_name; #endif //目前它仅用于表示是否需要清理资源,相当于一个标志位,实际不会调用到它所指向的方法 ngx_http_cleanup_pt *cleanup; //是否指定文件缓存路径的标志位 //xxx_store(例如scgi_store) on | off |path 只要不是off,store都为1,赋值见ngx_http_fastcgi_store //制定了存储前端文件的路径,参数on指定了将使用root和alias指令相同的路径,off禁止存储,此外,参数中可以使用变量使路径名更明确:fastcgi_store /data/www$original_uri; unsigned store:1; //ngx_http_upstream_init_request赋值 //后端应答数据在ngx_http_upstream_process_request->ngx_http_file_cache_update中进行缓存 //ngx_http_test_predicates用于可以检测xxx_no_cache,从而决定是否需要缓存后端数据 /*如果Cache-Control参数值为no-cache、no-store、private中任意一个时,则不缓存...不缓存... 后端携带有"x_accel_expires:0"头 参考http://blog.csdn.net/clh604/article/details/9064641 部行也可能置0,参考ngx_http_upstream_process_accel_expires,不过可以通过fastcgi_ignore_headers忽略这些头部,从而可以继续缓存*/ //此外,如果没有使用fastcgi_cache_valid proxy_cache_valid 设置生效时间,则默认会把cacheable置0,见ngx_http_upstream_send_response unsigned cacheable:1; //是否启用文件缓存 参考http://blog.csdn.net/clh604/article/details/9064641 unsigned accel:1; unsigned ssl:1; //是否基于SSL协议访问上游服务器 #if (NGX_HTTP_CACHE) unsigned cache_status:3; //NGX_HTTP_CACHE_BYPASS 等 #endif /* upstream有3种处理上游响应包体的方式,但HTTP模块如何告诉upstream使用哪一种方式处理上游的响应包体呢? 当请求的ngx_http_request_t结构体中subrequest_in_memory标志位为1时,将采用第1种方式,即upstream不转发响应包体到下游,由HTTP模 块实现的input_filter方法处理包体; 当subrequest_in_memory为0时,upstream会转发响应包体。 当ngx_http_upstream_conf t配置结构体中的buffering标志位为1时,将开启更多的内存和磁盘文件用于缓存上游的响应包体,这意味上游网速更快; 会先buffer后端FCGI发过来的数据,等达到一定量(比如buffer满)再传送给最终客户端 当buffering为0时,将使用固定大小的缓冲区(就是上面介绍的buffer缓冲区)来转发响应包体。 在向客户端转发上游服务器的包体时才有用。 当buffering为1时,表示使用多个缓冲区以及磁盘文件来转发上游的响应包体。 当Nginx与上游间的网速远大于Nginx与下游客户端间的网速时,让Nginx开辟更多的内存甚至使用磁盘文件来缓存上游的响应包体, 这是有意义的,它可以减轻上游服务器的并发压力。 当buffering为0时,表示只使用上面的这一个buffer缓冲区来向下游转发响应包体 从上游接收多少就向下游发送多少,不缓存,这样上游发送速率与下游速率相等 */ //fastcgi赋值见ngx_http_fastcgi_handler u->buffering = flcf->upstream.buffering; //见xxx_buffering如fastcgi_buffering 是否缓存后端服务器应答回来的包体 //该参数也可以通过后端返回的头部字段: X-Accel-Buffering:no | yes来设置是否开启,见ngx_http_upstream_process_buffering /* 如果开启缓冲,那么Nginx将尽可能多地读取后端服务器的响应数据,等达到一定量(比如buffer满)再传送给最终客户端。如果关闭, 那么Nginx对数据的中转就是一个同步的过程,即从后端服务器接收到响应数据就立即将其发送给客户端。 buffering标志位为1时,将开启更多的内存和磁盘文件用于缓存上游的响应包体,这意味上游网速更快;当buffering 为0时,将使用固定大小的缓冲区(就是上面介绍的buffer缓冲区)来转发响应包体。 */ //buffering方式和非buffering方式在函数ngx_http_upstream_send_response分叉 //见xxx_buffering如fastcgi_buffering proxy_buffering 是否缓存后端服务器应答回来的包体 unsigned buffering:1; //向下游转发上游的响应包体时,是否开启更大的内存及临时磁盘文件用于缓存来不及发送到下游的响应 //为1说明本次和后端的连接使用的是缓存cache(keepalive配置)connection的TCP连接,也就是使用的是之前已经和后端建立好的TCP连接ngx_connection_t //在缓存和后端的连接的时候使用(也就是是否配置了keepalive con-num配置项),为1表示使用的是缓存的TCP连接,为0表示新建的和后端的TCP连接,见ngx_http_upstream_free_keepalive_peer //此外,在后端服务器交互包体后,如果头部行指定没有包体,则会u->keepalive = !u->headers_in.connection_close;例如ngx_http_proxy_process_header unsigned keepalive:1;//只有在开启keepalive con-num才有效,释放后端tcp连接判断在ngx_http_upstream_free_keepalive_peer unsigned upgrade:1; //后端返回//HTTP/1.1 101的时候置1 /* request_sent表示是否已经向上游服务器发送了请求,当request_sent为1时,表示upstream机制已经向上游服务器发送了全部或者部分的请求。 事实上,这个标志位更多的是为了使用ngx_output_chain方法发送请求,因为该方法发送请求时会自动把未发送完的request_bufs链表记录下来, 为了防止反复发送重复请求,必须有request_sent标志位记录是否调用过ngx_output_chain方法 */ unsigned request_sent:1; //ngx_http_upstream_send_request_body中发送请求包体到后端的时候置1 /* 将上游服务器的响应划分为包头和包尾,如果把响应直接转发给客户端,header_sent标志位表示包头是否发送,header_sent为1时表示已经把 包头转发给客户端了。如果不转发响应到客户端,则header_sent没有意义 */ unsigned header_sent:1; //表示头部已经扔给协议栈了, };
/* 事实上,HTTP反向代理模块在nginx.conf文件中提供的配置项大都是用来设置ngx_http_upstream_conf_t结构体中的成员的。 */ //在解析到upstream{}配置的时候,创建该结构,和location{}类似 typedef struct { //upstream配置包括proxy fastcgi wcgi等都用该结构 //当在ngx_http_upstream_t结构体中没有实现resolved成员时,upstream这个结构体才会生效,它会定义上游服务器的配置 ngx_http_upstream_srv_conf_t *upstream; //默认60s ngx_msec_t connect_timeout;//建立TCP连接的超时时间,实际上就是写事件添加到定时器中时设显的超时时间 //xxx_send_timeout(fastcgi memcached proxy) 默认60s ngx_msec_t send_timeout;//发送请求的超时时间。通常就是写事件添加到定时器中设置的超时时间 //fastcgi_read_timeout XXX_read_timeout设置 当本端发送客户端请求包体给后端服务器后,等待后端服务器响应的超时时间 ngx_msec_t read_timeout;//接收响应的超时时间。通常就是读事件添加到定时器中设置的超时时间 ngx_msec_t timeout; ngx_msec_t next_upstream_timeout; size_t send_lowat; //TCP的SO_SNDLOWAT选项,表示发送缓冲区的下限 fastcgi_send_lowat proxy_send_lowat //定义了接收头部的缓冲区分配的内存大小(ngx_http_upstream_t中的buffer缓冲区),当不转发响应给下游或者在buffering标志位为0 //的情况下转发响应时,它同样表示接收包体的缓冲区大小 当接收后端过来的头部信息的时候先分配这么多空间来接收头部行等信息,见ngx_http_upstream_process_header //头部行部分(也就是第一个fastcgi data标识信息,里面也会携带一部分网页数据)的fastcgi标识信息开辟的空间用buffer_size配置指定 //指定的大小空间开辟在ngx_http_upstream_process_header size_t buffer_size; //xxx_buffer_size(fastcgi_buffer_size proxy_buffer_size memcached_buffer_size) size_t limit_rate;//默认值0 fastcgi_limit_rate 或者proxy memcached等进行限速配置 限制的是与客户端浏览器的速度,不是与后端的速度 //仅当buffering标志位为1,并且向下游转发响应时生效。它会设置到ngx_event_pipe_t结构体的busy_size成员中 //在buffering方式下,本地最多换成还没有发送到客户端的网页包体大小,在ngx_event_pipe_write_to_downstream生效 //默认值为buffer_size的两倍,实际上总共为后端开辟的空间为buffer_size+ 5*3k(fastcgi_buffers 5 3K) //p->busy_size = u->conf->busy_buffers_size; size_t busy_buffers_size; //xxx_busy_buffers_size fastcgi_busy_buffers_size 默认值为buffer_size的两倍 /* 在buffering标志位为1时,如果上游速度快于下游速度,将有可能把来自上游的响应存储到临时文件中,而max_temp_file_size指定了临时文件的 最大长度。实际上,它将限制ngx_event_pipe_t结构体中的temp_file fastcgi_max_temp_file_size配置 */ size_t max_temp_file_size; //fastcgi_max_temp_file_size XXX_max_temp_file_size size_t temp_file_write_size; //fastcgi_temp_file_write_size配置表示将缓冲区中的响应写入临时文件时一次写入字符流的最大长度 //可以通过xxx_busy_buffers_size(proxy_busy_buffers_size)等设置,默认值为2*buffer_size size_t busy_buffers_size_conf; //被赋值给busy_buffers_size /* 在buffering标志位为1时,如果上游速度快于下游速度,将有可能把来自上游的响应存储到临时文件中,而max_temp_file_size指定了临时文件的 最大长度。实际上,它将限制ngx_event_pipe_t结构体中的temp_file */ size_t max_temp_file_size_conf; size_t temp_file_write_size_conf;////表示将缓冲区中的响应写入临时文件时一次写入字符流的最大长度 //真正分配空间在//在ngx_event_pipe_read_upstream中创建空间 ngx_bufs_t bufs;//以缓存响应的方式转发上游服务器的包体时所使用的内存大小 //例如fastcgi_buffers 5 3K /* 针对ngx_http_upstream_t结构体中保存解析完的包头的headers in成员,ignore_headers可以按照二进制位使得upstream在转发包头时跳过对某些头部 的处理。作为32位整型,理论上ignore_headers最多可以表示32个需要跳过不予处理的头部,然而目前upstream机制仅提供8个位用于忽略8个HTTP头部的处 理,包括: #define NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT 0x00000002 #define NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES 0x00000004 #define NGX_HTTP_UPSTREAM_IGN_EXPIRES 0x00000008 #define NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL 0x00000010 #define NGX_HTTP_UPSTREAM_IGN_SET_COOKIE 0x00000020 #define NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE 0x00000040 #define NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING 0x00000080 #define NGX_HTTP_UPSTREAM_IGN_XA_CHARSET 0x00000100 #define NGX_HTTP_UPSTREAM_IGN_VARY 0x00000200 */ ngx_uint_t ignore_headers; /* 以二进制位来表示一些错误码,如果处理上游响应时发现这些错误码,那么在没有将响应转发给下游客户端时,将会选择下 一个上游服务器来重发请求。参见ngx_http_upstream_next方法 */ ngx_uint_t next_upstream; /* 在buffering标志为1的情况下转发响应时,将有可能把响应存放到临时文件中。在ngx_http_upstream_t中的store标志位为1时, store_access表示所创建的目录、文件的权限 */ ngx_uint_t store_access; //XXX_next_upstream_tries,例如fastcgi_next_upstream_tries ngx_uint_t next_upstream_tries; /* 决定转发响应方式的标志位,buffering为1时表示打开缓存,这时认为上游的网速快于下游的网速,会尽量地在内存或者磁盘中缓存来自上游的 响应;如果buffering为0,仅会开辟一块固定大小的内存块作为缓存来转发响应 */ //默认为1 request_buffering是否缓存客户端到后端的包体 buffering是否缓存后端到客户端浏览器的包体 ngx_flag_t buffering; //见xxx_buffering如fastcgi_buffering 是否换成后端服务器应答回来的包体 //默认1 request_buffering是否缓存客户端到后端的包体 buffering是否缓存后端到客户端浏览器的包体 ngx_flag_t request_buffering;//是否换成客户端请求的包体 XXX_request_buffering (例如proxy_request_buffering fastcgi_request_buffering //proxy_pass_request_headers fastcgi_pass_request_headers设置是否转发HTTP头部。 ngx_flag_t pass_request_headers;////是否转发客户端浏览器过来的请求头部到后端去 ngx_flag_t pass_request_body; ////是否转发客户端浏览器过来的包体到后端去 /* 表示标志位。当它为1时,表示与上游服务器交互时将不检查Nginx与下游客户端间的连接是否断开。 也就是说,即使下游客户端主动关闭了连接,也不会中断与上游服务器间的交互,见ngx_http_upstream_init_request */ //默认off ngx_flag_t ignore_client_abort; //fastcgi_ignore_client_abort ON | OFF /* 当解析上游响应的包头时,如果解析后设置到headers_in结构体中的status_n错误码大于400,则会试图把它与error_page中指定的错误码相匹配, 如果匹配上,则发送error_page中指定的响应,否则继续返回上游服务器的错误码。详见ngx_http_upstream_intercept_errors方法 */ ngx_flag_t intercept_errors; /* buffering标志位为1的情况下转发响应时才有意义。这时,如果cyclic_temp_file为l,则会试图复用临时文件中已经使用过的空间。不建议 将cyclic_temp_file设为1 */ //默认0 ngx_flag_t cyclic_temp_file; //fastcgi_cyclic_temp_file XXX_cyclic_temp_file ngx_flag_t force_ranges; //xxx_temp_path fastcgi_temp_path配置 默认值ngx_http_fastcgi_temp_path ngx_path_t *temp_path; //在buff ering标志位为1的情况下转发响应时,存放临时文件的路径 /* 不转发的头部。实际上是通过ngx_http_upstream_hide_headers_hash方法,根据hide_headers和pass_headers动态数组构造出的需要隐藏的HTTP头部散列表 */ //这里面存储的是ngx_http_xxx_hide_headers如ngx_http_fastcgi_hide_headers ngx_http_proxy_hide_headers等 ngx_hash_t hide_headers_hash; //把default_hide_headers(ngx_http_proxy_hide_headers ngx_http_fastcgi_hide_headers)中的成员做hash保存到conf->hide_headers_hash /* hide_headers的类型是ngx_array_t动态数组(实际上,upstream模块将会通过hide_headers来构造hide_headers_hash散列表)。 由于upstream模块要求hide_headers不可以为NULL,所以必须要初始化hide_headers成员。upstream模块提供了 ngx_http_upstream_hide_headers hash方法来初始化hide_headers,但仅可用在合并配置项方法内。 */ //XXX_pass_headers XXX_hide_headers出现重叠冲突,则以hide_header为准,见ngx_http_upstream_hide_headers_hash //当转发上游响应头部(ngx_http_upstream_t中headers_in结构体中的头部)给下游客户端时如果不希望某些头部转发给下游,就设置到hide_headers动态数组中 ngx_array_t *hide_headers; //proxy_hide_header fastcgi_hide_header /* 当转发上游响应头部(ngx_http_upstream_t中headers_in结构体中的头部)给下游客户端时,upstream机制默认不会转发如“Date”、“Server”之 类的头部,如果确实希望直接转发它们到下游,就设置到pass_headers动态数组中 */ //XXX_pass_headers XXX_hide_headers出现重叠冲突,则以hide_header为准,见ngx_http_upstream_hide_headers_hash ngx_array_t *pass_headers; // proxy_hide_header fastcgi_hide_header ngx_http_upstream_local_t *local;//连接上游服务器时便用的本机地址 //proxy_bind fastcgi_bind 设置的本地IP端口地址,有可能设备有好几个eth,只用其中一个 #if (NGX_HTTP_CACHE) //xxx_cache(proxy_cache fastcgi_cache) abc必须xxx_cache_path(proxy_cache_path fastcgi_cache_path) xxx keys_zone=abc:10m;一起,否则在ngx_http_proxy_merge_loc_conf会失败,因为没有为该abc创建ngx_http_file_cache_t //如果配置的proxy_cache xxx中不带变量,则会从cycle->shared_memory中获取一个ngx_shm_zone_t并赋值,注意这个共享zone结构只有名字,没有直达长度 见ngx_http_proxy_cache //fastcgi_cache 指令指定了在当前作用域中使用哪个缓存维护缓存条目,参数对应的缓存必须事先由 fastcgi_cache_path 指令定义。 ngx_shm_zone_t *cache_zone; //如果只设置xxx_cache abc(proxy fascgi_cache)则,ngx_shm_zone_t->data为NULL,必须xxx_cache_path再次设置下该abc,否则会出错 //如果proxy_cache xxx$ss配置中带有变量等则配置的value字符串保存在cache_value中,见ngx_http_proxy_cache ngx_http_complex_value_t *cache_value; //依赖proxy_cache_path 见ngx_http_upstream_cache_get //Proxy_cache_min_uses number 默认为1,当客户端发送相同请求达到规定次数后,nginx才对响应数据进行缓存; ngx_uint_t cache_min_uses; //cache_min_uses //nginx何时从代理缓存中提供一个过期的响应,可以配合ngx_http_upstream_cache阅读 /* 例如如果设置了fastcgi_cache_use_stale updating,表示说虽然该缓存文件失效了,已经有其他客户端请求在获取后端数据,但是该客户端请求现在还没有获取完整, 这时候就可以把以前过期的缓存发送给当前请求的客户端 //可以配合ngx_http_upstream_cache阅读 */ ngx_uint_t cache_use_stale; //XXX_cache_use_stale(proxy fastcgi_cache_use_stale设置) //proxy |fastcgi _cache_methods POST GET HEAD; 赋值为位操作,见ngx_http_upstream_cache_method_mask中NGX_HTTP_HEAD等 ngx_uint_t cache_methods;//默认 proxy_cache_methods GET HEAD; /* When enabled, only one request at a time will be allowed to populate a new cache element identified according to the proxy_cache_key directive by passing a request to a proxied server. Other requests of the same cache element will either wait for a response to appear in the cache or the cache lock for this element to be released, up to the time set by the proxy_cache_lock_timeout directive. 这个主要解决一个问题: //proxy_cache_lock 默认off 0 //proxy_cache_lock_timeout 设置,默认5S 假设现在又两个客户端,一个客户端正在获取后端数据,并且后端返回了一部分,则nginx会缓存这一部分,并且等待所有后端数据返回继续缓存。 但是在缓存的过程中如果客户端2页来想后端去同样的数据uri等都一样,则会去到客户端缓存一半的数据,这时候就可以通过该配置来解决这个问题, 也就是客户端1还没缓存完全部数据的过程中客户端2只有等客户端1获取完全部后端数据,或者获取到proxy_cache_lock_timeout超时,则客户端2只有从后端获取数据 */ //参考http://blog.csdn.net/brainkick/article/details/8583335 ngx_flag_t cache_lock;//proxy_cache_lock 默认off 0 ngx_msec_t cache_lock_timeout;//proxy_cache_lock_timeout 设置,默认5S ngx_msec_t cache_lock_age; ngx_flag_t cache_revalidate; /* 语法:proxy_cache_valid reply_code [reply_code ...] time; proxy_cache_valid 200 302 10m; proxy_cache_valid 301 1h; proxy_cache_valid any 1m; */ ngx_array_t *cache_valid; //最终赋值给ngx_http_cache_t->valid_sec //xxx_cache_bypass xx1 xx2设置的xx2不为空或者不为0,则不会从缓存中取,而是直接冲后端读取 //xxx_no_cache xx1 xx2设置的xx2不为空或者不为0,则后端回来的数据不会被缓存 //ngx_http_set_predicate_slot设置 xxx_cache_bypass xx1 xx2中的xx1 xxx2到no_cache数组中 //proxy_cache_bypass fastcgi_cache_bypass 调用ngx_http_set_predicate_slot赋值,在ngx_http_test_predicates解析 ngx_array_t *cache_bypass; //ngx_http_set_predicate_slot设置 xxx_no_cache xx1 xx2中的xx1 xxx2到no_cache数组中在ngx_http_test_predicates解析 ngx_array_t *no_cache; #endif /* 当ngx_http_upstream_t中的store标志位为1时,如果需要将上游的响应存放到文件中,store_lengths将表示存放路径的长度,而store_values表示存放路径 */ ngx_array_t *store_lengths; ngx_array_t *store_values; #if (NGX_HTTP_CACHE) //fastcgi_store和fastcgi_cache只能配置其中一个,否则会包错 signed cache:2; //fastcgi_cache off该值为0 否则为1,见ngx_http_fastcgi_cache #endif //xxx_store(例如scgi_store) on | off |path 只要不是off,store都为1,赋值见ngx_http_fastcgi_store //制定了存储前端文件的路径,参数on指定了将使用root和alias指令相同的路径,off禁止存储,此外,参数中可以使用变量使路径名更明确:fastcgi_store /data/www$original_uri; signed store:2;//到目前为止,store标志位的意义与ngx_http_upstream_t中的store相同,仍只有o和1被使用到 /* 上面的intercept_errors标志位定义了400以上的错误码将会与error_page比较后再行处理,实际上这个规则是可以有一个例外情况的,如果将intercept_404 标志位设为1,当上游返回404时会直接转发这个错误码给下游,而不会去与error_page进行比较 */ unsigned intercept_404:1; /* 当该标志位为1时,将会根据ngx_http_upstream_t中headers_in结构体里的"X-Accel-Buffering"头部(它的值会是yes和no)来改变buffering 标志位,当其值为yes时,buffering标志位为1。因此,change_buffering为1时将有可能根据上游服务器返回的响应头部,动态地决定是以上 游网速优先还是以下游网速优先 */ unsigned change_buffering:1; #if (NGX_HTTP_SSL) ngx_ssl_t *ssl; ngx_flag_t ssl_session_reuse; ngx_http_complex_value_t *ssl_name; ngx_flag_t ssl_server_name; ngx_flag_t ssl_verify; #endif ngx_str_t module; //使用upstream的模块名称,仅用于记录日志 } ngx_http_upstream_conf_t;
启动upstream
在 NGX_HTTP_FIND_CONFIG_PHASE 阶段(ngx_http_core_find_config_phase),nginx 在根据 uri 匹配对应的 location 并初始化请求结构对应字段的过程中,对于指定了启动函数的 module,会将启动函数赋值给请求描述结构的 content_handler 字段而在生成 HTTP 响应的 NGX_HTTP_CONTENT_PHASE 阶段(ngx_http_core_content_phase),首先执行的就是:
if (r->content_handler) { r->write_event_handler = ngx_http_request_empty_handler; ngx_http_finalize_request(r, r->content_handler(r)); return NGX_OK; }
当收到请求后,http的代理模块是ngx_http_proxy_module,其NGX_HTTP_CONTENT_PHASE阶段的处理函数为ngx_http_proxy_handler;
在 ngx_http_proxy_module 模块的 ngx_http_proxy_merge_loc_conf 函数中,如果转发方式是 upstream,他将会将 location 描述结构中启动函数
clcf->handler = ngx_http_proxy_handler 设为 ngx_http_proxy_handler,因此这里调用的 r->content_handler 就是 ngx_http_proxy_handler 函数
//配置proxy_pass后,在ngx_http_core_content_phase里面指向该函数 /* 那么,当有请求访问到特定的location的时候(假设这个location配置了proxy_pass指令), 跟其他请求一样,会调用各个phase的checker和handler,到了NGX_HTTP_CONTENT_PHASE的checker, 即ngx_http_core_content_phase()的时候,会调用r->content_handler(r),即ngx_http_proxy_handler。 */ static ngx_int_t ngx_http_proxy_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_http_upstream_t *u; ngx_http_proxy_ctx_t *ctx; ngx_http_proxy_loc_conf_t *plcf; #if (NGX_HTTP_CACHE) ngx_http_proxy_main_conf_t *pmcf; #endif if (ngx_http_upstream_create(r) != NGX_OK) { //生成一个ngx_http_upstream_t结构,赋值到r->upstream return NGX_HTTP_INTERNAL_SERVER_ERROR; } ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_ctx_t)); if (ctx == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } //将这个上下文放到请求的ctx数组的ngx_http_proxy_module模块中,r->ctx[module.ctx_index] = c; ngx_http_set_ctx(r, ctx, ngx_http_proxy_module); plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); u = r->upstream; if (plcf->proxy_lengths == NULL) { ctx->vars = plcf->vars; u->schema = plcf->vars.schema; #if (NGX_HTTP_SSL) u->ssl = (plcf->upstream.ssl != NULL); #endif } else { if (ngx_http_proxy_eval(r, ctx, plcf) != NGX_OK) { //获取uri中变量的值,从而可以得到完整的uri,和ngx_http_proxy_pass配合阅读 return NGX_HTTP_INTERNAL_SERVER_ERROR; } } u->output.tag = (ngx_buf_tag_t) &ngx_http_proxy_module; u->conf = &plcf->upstream; #if (NGX_HTTP_CACHE) pmcf = ngx_http_get_module_main_conf(r, ngx_http_proxy_module); u->caches = &pmcf->caches; u->create_key = ngx_http_proxy_create_key; #endif //为upstream准备各种请求的回调 u->create_request = ngx_http_proxy_create_request; //生成发送到上游服务器的请求缓冲(或者一条缓冲链),也就是要发给上游的数据 u->reinit_request = ngx_http_proxy_reinit_request; //处理回调就是第一行的回调,第一行处理完后会设置为ngx_http_proxy_process_header,走下一步 u->process_header = ngx_http_proxy_process_status_line; //一个upstream的u->read_event_handler 读事件回调被设置为ngx_http_upstream_process_header;,他会不断的读取数据,然后 //调用process_header对于FCGI,当然是调用对应的读取FCGI格式的函数了,对于代理模块,只要处理HTTP格式即可 u->abort_request = ngx_http_proxy_abort_request; u->finalize_request = ngx_http_proxy_finalize_request; r->state = 0; if (plcf->redirects) { u->rewrite_redirect = ngx_http_proxy_rewrite_redirect; } if (plcf->cookie_domains || plcf->cookie_paths) { u->rewrite_cookie = ngx_http_proxy_rewrite_cookie; } u->buffering = plcf->upstream.buffering; u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t)); if (u->pipe == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } //设置在有buffering的状态下,nginx读取upstream的回调,如果是FCGI,则是对应的回调ngx_http_fastcgi_input_filter用来解析FCGI协议的数据。 //如果我们要实现我们自己的协议格式,那就用对应的解析方式。 u->pipe->input_filter = ngx_http_proxy_copy_filter; u->pipe->input_ctx = r; //buffering后端响应包体使用ngx_event_pipe_t->input_filter 非buffering方式响应后端包体使用ngx_http_upstream_s->input_filter ,在ngx_http_upstream_send_response分叉 u->input_filter_init = ngx_http_proxy_input_filter_init; u->input_filter = ngx_http_proxy_non_buffered_copy_filter; u->input_filter_ctx = r; u->accel = 1; if (!plcf->upstream.request_buffering && plcf->body_values == NULL && plcf->upstream.pass_request_body && (!r->headers_in.chunked || plcf->http_version == NGX_HTTP_VERSION_11)) { r->request_body_no_buffering = 1; } /* 在阅读HTTP反向代理模块(ngx_http_proxy_module)源代码时,会发现它并没有调用r->main->count++,其中proxy模块是这样启动upstream机制的: ngx_http_read_client_request_body(r,ngx_http_upstream_init);,这表示读取完用户请求的HTTP包体后才会调用ngx_http_upstream_init方法 启动upstream机制。由于ngx_http_read_client_request_body的第一行有效语句是r->maln->count++,所以HTTP反向代理模块不能 再次在其代码中执行r->main->count++。 这个过程看起来似乎让人困惑。为什么有时需要把引用计数加1,有时却不需要呢?因为ngx_http_read- client_request_body读取请求包体是 一个异步操作(需要epoll多次调度方能完成的可称其为异步操作),ngx_http_upstream_init方法启用upstream机制也是一个异步操作,因此, 从理论上来说,每执行一次异步操作应该把引用计数加1,而异步操作结束时应该调用ngx_http_finalize_request方法把引用计数减1。另外, ngx_http_read_client_request_body方法内是加过引用计数的,而ngx_http_upstream_init方法内却没有加过引用计数(或许Nginx将来会修改 这个问题)。在HTTP反向代理模块中,它的ngx_http_proxy_handler方法中用“ngx_http_read- client_request_body(r,ngx_http_upstream_init);” 语句同时启动了两个异步操作,注意,这行语句中只加了一次引用计数。执行这行语句的ngx_http_proxy_handler方法返回时只调用 ngx_http_finalize_request方法一次,这是正确的。对于mytest模块也一样,务必要保证对引用计数的增加和减少是配对进行的。 */ rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } return NGX_DONE; }
主要工作是调用 ngx_http_upstream_create 创建初始化请求描述结构中的 upstream 字段,即创建 upstream 描述结构紧接着对 upstream 描述结构中必要的字段和回调函数进行赋值和初始化然后通过 ngx_http_read_client_request_body 函数调用 ngx_http_upstream_init 函数实现 upstream 的连接和通信,并生成 HTTP 响应返回
upstream 机制的启动 -- ngx_http_upstream_init
在调用 ngx_http_upstream_create 创建 ngx_http_upstream_t 结构并进行了一系列初始化以后,就要开始真正的 upstream 的执行了ngx_http_proxy_handler 调用了 ngx_http_read_client_request_body 函数通过执行 ngx_http_upstream_init 实现 upstream 的初始化
//HTTP包体的长度有可能非常大,如果试图一次性调用并读取完所有的包体,那么多半会阻塞Nginx进程。HTTP框架提供了一种方法来异步地接收包体: /* ngx_http_read_client_request_body是一个异步方法,调用它只是说明要求Nginx开始接收请求的包体,并不表示是否已经接收完,当接收完所有的包体内容后, post_handler指向的回调方法会被调用。因此,即使在调用了ngx_http_read_client_request_body方法后它已经返回,也无法确定这时是否已经调用过post_handler 指向的方法。换句话说,ngx_http_read_client_request_body返回时既有可能已经接收完请求中所有的包体(假如包体的长度很小),也有可能还没开始接收包体。 如果ngx_http_read_client_request_body是在ngx_http_mytest_handler处理方法中调用的,那么后者一般要返回NGX_DONE,因为下一步就是将它的返回值作为参数 传给ngx_http_finalize_request。 */ //包体接收完毕后会执行回调方法ngx_http_client_body_handler_pt post_handler //ngx_http_parse_request_line解析请求行, ngx_http_process_request_headers(ngx_http_parse_header_line)解析头部行(请求头部) 接收包体ngx_http_read_client_request_body /* 调用了ngx_http_read_client_request_body方法就相当于启动了接收包体这一动作,在这个动作完成后,就会回调HTTP模块定义的post_handler方法 丢弃包体时,HTTP框架提供的方法是ngx_http_discard_request_body */ /* HTTP框架提供了两种方式处理HTTP包体,当然,这两种方式保持了完全无阻塞的事件驱动机制,非常高效。第一种方式就是把请求中的包体 接收到内存或者文件中,当然,由于包体的长度是可变的,同时内存又是有限的,因此,一般都是将包体存放到文件中。第二种方式是选择丢弃包体, 注意,丢弃不等于可以不接收包体,这样做可能会导致客户端出现发送请求超时的错误,所以,这个丢弃只是对于HTTP模块而言的,HTTP框架还是需 要“尽职尽责”地接收包体,在接收后直接丢弃。 */ ngx_int_t //一般都是需要访问上游服务器的时候才会读取包体,例如ngx_http_proxy_handler , /* 一般都是如果解析头部行后,后面有携带包体,则会走到这里,如果包体还没读完,下次也不会走到该函数,而是走ngx_http_do_read_client_request_body 实际上走到这里面的包体内容是在读取头部的时候,一起读出来的,读取地方见ngx_http_wait_request_handler 在NGX_HTTP_CONTENT_PHASE阶段通过ngx_http_core_content_phase调用content阶段的handler从而执行ngx_http_proxy_handler ngx_http_redis2_handler ngx_http_fastcgi_handler等,在这些函数中开始读取包体 */ ngx_http_read_client_request_body(ngx_http_request_t *r, //只有在连接后端服务器的时候才会读取客户端请求包体,见ngx_http_xxx_handler(proxy fastcgi等) ngx_http_client_body_handler_pt post_handler) //post_handler在ngx_http_do_read_client_request_body接收完所有包体后执行,或者在本函数能读取完包体后也会执行 //post_handler方法被回调时,务必调用类似ngx_http_finalize_request的方法去结束请求,否则引用计数会始终无法清零,从而导致请求无法释放。 { size_t preread; ssize_t size; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out, *cl; ngx_http_request_body_t *rb; ngx_http_core_loc_conf_t *clcf; /* 这里开辟真正的读取数据的空间后,buf的指针指向终端空间的头尾以及解析完的数据的位置, buf1 buf2 buf3 _________________________________________________________________________________ | | | | |__________________________|_________________________|___________________________| 1.第一次开辟好存储数据的空间ngx_create_temp_buf后,r->request_body->buf pos last start指向buf1的头部,end指向buf3尾部 2.假设第一次读取完内核协议栈的数据后填充好了buf1,r->request_body->buf中的pos start指向buf1的头部,last指向buf1尾部(buf2头部),end指向buf3尾部 3.开始调用ngx_http_request_body_filter,在该函数里面会重新分配一个ngx_buf_t,把r->request_body->buf成员赋值给她。然后把这个新的ngx_buf_t 添加到r->request_body->bufs链表中。赋值完后r->request_body->buf中的start指向buf1的头部,pos last指向buf1尾部(buf2头部),end指向buf3尾部 4.从复上面的2 3步骤 5.当解析完buf3的内容后,发现r->request_body->buf从内核读取到buf空间中的网络数据包已经被三个新的ngx_buf_t指向,并且这三个ngx_buf_t 通过r->request_body->bufs链表连接在了一起,这时候r->request_body->buf中的end = last,也就是所有ngx_create_temp_buf开辟的内存空间 已经存满了(recv的数据存在该空间里面),并且数据分成三个ngx_buf_t指向这些空间,然后连接到了转存到了r->request_body->bufs链表上。在 6.ngx_http_request_body_save_filter中检测到rb->buf->last == rb->buf->end,上面的buf(buf1+buf2+buf3)已经填满,然后通过r->request_body->bufs 把三个ngx_buf_t指向的内存空间一次性写入临时文件,写入临时文件后,r->request_body->buf中的pos last指针重新指向头部,又可以从新从 内核协议栈读取数据存储在里面了,然后从复1-5的过程 */ /* 首先把该请求对应的原始请求的引用计数加l。这同时是在要求每一个HTTP模块在传入的post_handler方法被回调时,务必调用类似 ngx_http_finalize_request的方法去结束请求,否则引用计数会始终无法清零,从而导致请求无法释放。 */ r->main->count++; //因为执行该函数一般都是向后端转发,例如可以参考ngx_http_read_client_request_body(r, ngx_http_upstream_init);,在ngx_http_upstream_init没有执行count++操作,实际上在这里 #if (NGX_HTTP_V2) /* HTTP2 data帧以外的所有帧的数据读取在ngx_http_v2_read_handler, data帧读取在ngx_http_read_client_request_body->ngx_http_v2_read_request_body */ if (r->stream && r == r->main) { r->request_body_no_buffering = 0; rc = ngx_http_v2_read_request_body(r, post_handler); goto done; } #endif /* 检查请求ngx_http_request_t结构体中的request_body成员,如果它已经被分配过了,证明已经读取过HTTP包体了,不需要再次读取一遍; 再检查请求ngx_http_request_t结构体中的discard_body标志位,如果discard_body为1,则证明曾经执行过丢弃包体的方法,现在包体正在被丢弃中。 只有这两个条件都不满足,才说明真正需要接收HTTP包体。 */ if (r != r->main || r->request_body || r->discard_body) { r->request_body_no_buffering = 0; post_handler(r); //直接执行各HTTP模块提供的post_handler回调方法 return NGX_OK; } if (ngx_http_test_expect(r) != NGX_OK) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } if (r->request_body_no_buffering) { //如果不缓存包体,request_body_no_buffering和request_body_in_file_only是互斥的 r->request_body_in_file_only = 0; //设置为不缓存包体,则就不能把包体写道文件中 } /* 分配请求的ngx_http_request_t结构体中的request_body成员(之前request_body是NULL空指针),准备接收包体。 */ rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); if (rb == NULL) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } /* * set by ngx_pcalloc(): * * rb->bufs = NULL; * rb->buf = NULL; * rb->free = NULL; * rb->busy = NULL; * rb->chunked = NULL; */ rb->rest = -1; rb->post_handler = post_handler; r->request_body = rb; //把创建的ngx_http_request_body_t空间赋值给request_body /* 检查请求的content-length头部,如果指定了包体长度的content-length字段小于或等于0,当然不用继续接收包体: 如果content-length大于0,则意味着继续执行,但HTTP模块定义的post_handler方法不会知道在哪一次事件的触发中会被回调, 所以先把它设置到request_body结构体的post_handler成员中。 */ if (r->headers_in.content_length_n < 0 && !r->headers_in.chunked) { r->request_body_no_buffering = 0; post_handler(r); // 调用传入的函数 return NGX_OK; } /* 接收HTTP头部的流程中,是有可能接收到HTTP包体的。首先我们需要检查在header_in缓冲区中已经接收到的包体长度,确定其是否大于或者等于 content-length头部指定的长度,如果大干或等于则说明已经接收到完整的包体 */ preread = r->header_in->last - r->header_in->pos; if (preread) { //注意在ngx_http_wait_request_handler中第一次读的时候默认是读1024字节,有可能ngx_http_wait_request_handler已经把包体读了 /* 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); out.buf = r->header_in;// out.next = NULL; //把最新读取到的buf数据添加到r->request_body->bufs中,并且让free指向该bufs中所有数据中已经解析了的数据节点信息(重复利用ngx_buf_t) //busy链表中的ngx_buf_t节点指向bufs中所有数据中还没有解析完毕的数据 rc = ngx_http_request_body_filter(r, &out); if (rc != NGX_OK) { goto done; } r->request_length += preread - (r->header_in->last - r->header_in->pos); /* 当上述条件不满足时,再检查header—in缓冲区里的剩余空闲空间是否可以存放下全部的包体(content-length头部指定),如果可以,就不用分配新的包体缓冲区浪费内存了 */ if (!r->headers_in.chunked && rb->rest > 0 //还需要读取rb->rest才能保证包体读完 && rb->rest <= (off_t) (r->header_in->end - r->header_in->last)) //判断header_in指向的剩余未用空间是否足够存取剩余的rest字节数据 { /* the whole request body may be placed in r->header_in */ //header_in中剩余的未用空间足够,例如还差rest = 1000字节才能读取完包体,但是header_in中剩余空间end - last超过1000,则不需要从新开辟空间 //直接使用header_in剩余空间,开辟新的ngx_buf_t空间,使用新的ngx_buf_t中的各个指针指向header_in中剩余未用空间,用来继续读取 b = ngx_calloc_buf(r->pool); if (b == NULL) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } b->temporary = 1; b->start = r->header_in->pos; b->pos = r->header_in->pos; b->last = r->header_in->last; b->end = r->header_in->end; rb->buf = b; r->read_event_handler = ngx_http_read_client_request_body_handler; r->write_event_handler = ngx_http_request_empty_handler; rc = ngx_http_do_read_client_request_body(r); goto done; } } else { /* set rb->rest */ if (ngx_http_request_body_filter(r, NULL) != NGX_OK) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } } if (rb->rest == 0) { //包体读取完毕 /* the whole request body was pre-read */ if (r->request_body_in_file_only) { //如果配置"client_body_in_file_only" on | clean 表示包体存储在磁盘文件中 if (ngx_http_write_request_body(r) != NGX_OK) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } if (rb->temp_file->file.offset != 0) { cl = ngx_chain_get_free_buf(r->pool, &rb->free); if (cl == NULL) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } b = cl->buf; ngx_memzero(b, sizeof(ngx_buf_t)); b->in_file = 1; b->file_last = rb->temp_file->file.offset; b->file = &rb->temp_file->file; rb->bufs = cl; //如果包体存入临时文件中,则读取包体完成后,bufs指向的ngx_chain_t中的各个指针指向文件中的相关偏移 } else { rb->bufs = NULL; } } r->request_body_no_buffering = 0; post_handler(r); return NGX_OK; } //只有读取包体执行一次到该下面流程,则表示读取一次的时候没有读取完 //包体长度出错 if (rb->rest < 0) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "negative request body rest"); rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); size = clcf->client_body_buffer_size; size += size >> 2; //实际上就是四分之五5/4个client_body_buffer_size /* TODO: honor r->request_body_in_single_buf */ //走到这里之前至少在ngx_http_wait_request_handler函数中读取过一次,也就是读取头部的时候,可能会读取一部分包体,在读取头部的时候 //读取的最大报文长度为client_header_buffer_size,所以包体有可能在那里读取后处理了头部行后,会走到本函数处理包体,这时候可能包体没有读完 if (!r->headers_in.chunked && rb->rest < size) { size = (ssize_t) rb->rest; if (r->request_body_in_single_buf) { //需要缓存到同一个buf中,那么开辟的空间就必须一次分配完,这样可以存储后面所有的。 size += preread; //如果是把读取的网络数据存到同一个single buffer中,则本次读到preread字节,但是还有size字节没读,所以需要相加,表示一共需要这么多空间, } } else { size = clcf->client_body_buffer_size; //如果不是缓存到同一个buf,则一次最多开辟这么多空间,这样可能需要多个buf才能读取完 } /* 说明确实需要分配用于接收包体的缓冲区了。缓冲区长度由nginx.conf丈件中的client_body_buffer_size配置项指定,缓冲区就在ngx_http_request_body_t 结构体的buf成员中存放着,同时,bufs和to_ write这两个缓冲区链表首部也指向该buf。 */ /* 这里开辟真正的读取数据的空间后,buf的指针指向终端空间的头尾以及解析完的数据的位置, buf1 buf2 buf3 _________________________________________________________________________________ | | | | |__________________________|_________________________|___________________________| 1.第一次开辟好存储数据的空间ngx_create_temp_buf后,r->request_body->buf pos last start指向buf1的头部,end指向buf3尾部 2.假设第一次读取完内核协议栈的数据后填充好了buf1,r->request_body->buf中的pos start指向buf1的头部,last指向buf1尾部(buf2头部),end指向buf3尾部 3.开始调用ngx_http_request_body_filter,在该函数里面会重新分配一个ngx_buf_t,把r->request_body->buf成员赋值给她。然后把这个新的ngx_buf_t 添加到r->request_body->bufs链表中。赋值完后r->request_body->buf中的start指向buf1的头部,pos last指向buf1尾部(buf2头部),end指向buf3尾部 4.从复上面的2 3步骤 5.当解析完buf3的内容后,发现r->request_body->buf从内核读取到buf空间中的网络数据包已经被三个新的ngx_buf_t指向,并且这三个ngx_buf_t 通过r->request_body->bufs链表连接在了一起,这时候r->request_body->buf中的end = last,也就是所有ngx_create_temp_buf开辟的内存空间 已经存满了(recv的数据存在该空间里面),并且数据分成三个ngx_buf_t指向这些空间,然后连接到了转存到了r->request_body->bufs链表上。在 6.ngx_http_request_body_save_filter中检测到rb->buf->last == rb->buf->end,上面的buf(buf1+buf2+buf3)已经填满,然后通过r->request_body->bufs 把三个ngx_buf_t指向的内存空间一次性写入临时文件,写入临时文件后,r->request_body->buf中的pos last指针重新指向头部,又可以从新从 内核协议栈读取数据存储在里面了,然后从复1-5的过程 //读取客户包体即使是存入临时文件中,当所有包体读取完毕后(ngx_http_do_read_client_request_body),还是会让r->request_body->bufs指向文件中的相关偏移内存地址 */ rb->buf = ngx_create_temp_buf(r->pool, size); //这个是为下次读取准备的 if (rb->buf == NULL) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; } /* 设置请求ngx_http_request_t结构体的read_ event_ handler成员为上面介绍过的ngx_http_read_client_request_body_handler方法, 它意味着如果epoll再次检测到可读事件或者读事件的定时器超时,HTTP框架将调用ngx_http_read_client_request_body_handler方法处理 */ r->read_event_handler = ngx_http_read_client_request_body_handler; r->write_event_handler = ngx_http_request_empty_handler; /* 调用ngx_http_do_read_client_request_body方法接收包体。该方法的意义在于把客户端与Nginx之间TCP连接上套接字缓冲区中的当前字符流全 部读出来,并判断是否需要写入文件,以及是否接收到全部的包体,同时在接收到完整的包体后激活post_handler回调方法 */ rc = ngx_http_do_read_client_request_body(r);//这里面添加ngx_handle_read_event的时候,对应的handler为ngx_http_read_client_request_body_handler done: if (r->request_body_no_buffering && (rc == NGX_OK || rc == NGX_AGAIN)) { if (rc == NGX_OK) { r->request_body_no_buffering = 0; } else { /* rc == NGX_AGAIN */ r->reading_body = 1; } r->read_event_handler = ngx_http_block_reading; post_handler(r); } if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {//如果返回出错 r->main->count--; //该函数处理结束后-1,因为该函数开始处理的时候有+1 } return rc; }
通过调用 post_handler 函数实现返回包体的生成
/* 1. 调用create_request创建fcgi或者proxy的数据结构。 2. 调用ngx_http_upstream_connect连接下游服务器。 */ static void ngx_http_upstream_init_request(ngx_http_request_t *r) { 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; if (r->aio) { return; } u = r->upstream;//ngx_http_upstream_create里面设置的 ngx_http_XXX_handler(ngx_http_proxy_handler)中执行 #if (NGX_HTTP_CACHE) if (u->conf->cache) { ngx_int_t rc; int cache = u->conf->cache; rc = ngx_http_upstream_cache(r, u); ngx_log_debugall(r->connection->log, 0, "ngx http cache, conf->cache:%d, rc:%d", cache, rc); if (rc == NGX_BUSY) { r->write_event_handler = ngx_http_upstream_init_request; return; } r->write_event_handler = ngx_http_request_empty_handler; if (rc == NGX_DONE) { return; } if (rc == NGX_ERROR) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (rc != NGX_DECLINED) { ngx_http_finalize_request(r, rc); return; } } #endif u->store = u->conf->store; /* 设置Nginx与下游客户端之间TCP连接的检查方法 实际上,这两个方法都会通过ngx_http_upstream_check_broken_connection方法检查Nginx与下游的连接是否正常,如果出现错误,就会立即终止连接。 */ /* 2025/04/24 05:31:47[ ngx_http_upstream_init, 654] [debug] 15507#15507: *1 < ngx_http_upstream_init, 653> epoll NGX_WRITE_EVENT(et) read add 2025/04/24 05:31:47[ ngx_epoll_add_event, 1400] [debug] 15507#15507: *1 epoll modify read and write event: fd:11 op:3 ev:80002005 2025/04/24 05:31:47[ ngx_epoll_process_events, 1624] [debug] 15507#15507: begin to epoll_wait, epoll timer: 60000 2025/04/24 05:31:47[ ngx_epoll_process_events, 1709] [debug] 15507#15507: epoll: fd:11 epoll-out(ev:0004) d:B26A00E8 实际上是通过ngx_http_upstream_init中的mod epoll_ctl添加读写事件触发的,当本次循环退回到ngx_worker_process_cycle ..->ngx_epoll_process_events 的时候,就会触发epoll_out,从而执行ngx_http_upstream_wr_check_broken_connection */ if (!u->store && !r->post_action && !u->conf->ignore_client_abort) { //注意这时候的r还是客户端的连接,与上游服务器的连接r还没有建立 r->read_event_handler = ngx_http_upstream_rd_check_broken_connection; //设置回调需要检测连接是否有问题。 r->write_event_handler = ngx_http_upstream_wr_check_broken_connection; } //有接收到客户端包体,则把包体结构赋值给u->request_bufs,在后面的if (u->create_request(r) != NGX_OK) {会用到 if (r->request_body) {//客户端发送过来的POST数据存放在此,ngx_http_read_client_request_body放的 u->request_bufs = r->request_body->bufs; //记录客户端发送的数据,下面在create_request的时候拷贝到发送缓冲链接表里面的。 } /* 调用请求中ngx_http_upstream_t结构体里由某个HTTP模块实现的create_request方法,构造发往上游服务器的请求 (请求中的内容是设置到request_bufs缓冲区链表中的)。如果create_request方法没有返回NGX_OK,则upstream结束 如果是FCGI。下面组建好FCGI的各种头部,包括请求开始头,请求参数头,请求STDIN头。存放在u->request_bufs链接表里面。 如果是Proxy模块,ngx_http_proxy_create_request组件反向代理的头部啥的,放到u->request_bufs里面 FastCGI memcached uwsgi scgi proxy都会用到upstream模块 */ if (u->create_request(r) != NGX_OK) { //ngx_http_XXX_create_request ngx_http_proxy_create_request等 ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } /* 获取ngx_http_upstream_t结构中主动连接结构peer的local本地地址信息 */ u->peer.local = ngx_http_upstream_get_local(r, u->conf->local); clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); /* 初始化ngx_http_upstream_t结构中成员output向下游发送响应的方式 */ 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; if (u->output.output_filter == NULL) { //设置过滤模块的开始过滤函数为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; //当请求结束时,一定会调用ngx_http_upstream_cleanup方法 cln->data = r;//指向所指的请求结构体。 u->cleanup = &cln->handler; /* http://www.pagefault.info/?p=251 然后就是这个函数最核心的处理部分,那就是根据upstream的类型来进行不同的操作,这里的upstream就是我们通过XXX_pass传递进来的值, 这里的upstream有可能下面几种情况。 Ngx_http_fastcgi_module.c (src\http\modules): { ngx_string("fastcgi_pass"), Ngx_http_memcached_module.c (src\http\modules): { ngx_string("memcached_pass"), Ngx_http_proxy_module.c (src\http\modules): { ngx_string("proxy_pass"), Ngx_http_scgi_module.c (src\http\modules): { ngx_string("scgi_pass"), Ngx_http_uwsgi_module.c (src\http\modules): { ngx_string("uwsgi_pass"), Ngx_stream_proxy_module.c (src\stream): { ngx_string("proxy_pass"), 1 XXX_pass中不包含变量。 2 XXX_pass传递的值包含了一个变量($开始).这种情况也就是说upstream的url是动态变化的,因此需要每次都解析一遍. 而第二种情况又分为2种,一种是在进入upstream之前,也就是 upstream模块的handler之中已经被resolve的地址(请看ngx_http_XXX_eval函数), 一种是没有被resolve,此时就需要upstream模块来进行resolve。接下来的代码就是处理这部分的东西。 */ if (u->resolved == NULL) {//上游的IP地址是否被解析过,ngx_http_fastcgi_handler调用ngx_http_fastcgi_eval会解析。 为NULL说明没有解析过,也就是fastcgi_pas xxx中的xxx参数没有变量 uscf = u->conf->upstream; //upstream赋值在ngx_http_fastcgi_pass } else { //fastcgi_pass xxx的xxx中有变量,说明后端服务器是会根据请求动态变化的,参考ngx_http_fastcgi_handler #if (NGX_HTTP_SSL) u->ssl_name = u->resolved->host; #endif //ngx_http_fastcgi_handler 会调用 ngx_http_fastcgi_eval函数,进行fastcgi_pass 后面的URL的简析,解析出unix域,或者socket. // 如果已经是ip地址格式了,就不需要再进行解析 if (u->resolved->sockaddr) {//如果地址已经被resolve过了,我IP地址,此时创建round robin peer就行 if (ngx_http_upstream_create_round_robin_peer(r, u->resolved) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } ngx_http_upstream_connect(r, u);//连接 return; } //下面开始查找域名,因为fcgi_pass后面不是ip:port,而是url; host = &u->resolved->host;//获取host信息。 // 接下来就要开始查找域名 umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); uscfp = umcf->upstreams.elts; for (i = 0; i < umcf->upstreams.nelts; i++) {//遍历所有的上游模块,根据其host进行查找,找到host,port相同的。 uscf = uscfp[i];//找一个IP一样的上流模块 if (uscf->host.len == host->len && ((uscf->port == 0 && u->resolved->no_port) || uscf->port == u->resolved->port) && ngx_strncasecmp(uscf->host.data, host->data, host->len) == 0) { goto found;//这个host正好相等 } } if (u->resolved->port == 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no port in upstream \"%V\"", host); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } //没办法了,url不在upstreams数组里面,也就是不是我们配置的,那么初始化域名解析器 temp.name = *host; // 初始化域名解析器 ctx = ngx_resolve_start(clcf->resolver, &temp);//进行域名解析,带缓存的。申请相关的结构,返回上下文地址。 if (ctx == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (ctx == NGX_NO_RESOLVER) {//无法进行域名解析。 // 返回NGX_NO_RESOLVER表示无法进行域名解析 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no resolver defined to resolve %V", host); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY); return; } // 设置需要解析的域名的类型与信息 ctx->name = *host; ctx->handler = ngx_http_upstream_resolve_handler;//设置域名解析完成后的回调函数。 ctx->data = r; ctx->timeout = clcf->resolver_timeout; u->resolved->ctx = ctx; //开始域名解析,没有完成也会返回的。 if (ngx_resolve_name(ctx) != NGX_OK) { u->resolved->ctx = NULL; ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } return; // 域名还没有解析完成,则直接返回 } found: if (uscf == NULL) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "no upstream configuration"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } #if (NGX_HTTP_SSL) u->ssl_name = uscf->host; #endif if (uscf->peer.init(r, uscf) != NGX_OK) {//ngx_http_upstream_init_round_XX_peer(ngx_http_upstream_init_round_robin_peer) ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } u->peer.start_time = ngx_current_msec; if (u->conf->next_upstream_tries && u->peer.tries > u->conf->next_upstream_tries) { u->peer.tries = u->conf->next_upstream_tries; } ngx_http_upstream_connect(r, u);//调用ngx_http_upstream_connect方法向上游服务器发起连接 }
与上游服务器建立连接
upstream机制与上游服务器之间通过tcp建立连接,为了保证三次握手的过程中不阻塞进程,Nginx采用了无阻塞的套接字来连接上游服务器。
ngx_http_upstream_connect负责发起建连动作,如果没有立即返回成功,需要在epoll中监控该套接字,当出现可写事件时,则说明连接已经建立成功。
/* upstream机制与上游服务器是通过TCP建立连接的,众所周知,建立TCP连接需要三次握手,而三次握手消耗的时间是不可控的。为了保证建立TCP 连接这个操作不会阻塞进程,Nginx使用无阻塞的套接字来连接上游服务器。调用的ngx_http_upstream_connect方法就是用来连接上游服务器的, 由于使用了非阻塞的套接字,当方法返回时与上游之间的TCP连接未必会成功建立,可能还需要等待上游服务器返回TCP的SYN/ACK包。因此, ngx_http_upstream_connect方法主要负责发起建立连接这个动作,如果这个方法没有立刻返回成功,那么需要在epoll中监控这个套接字,当 它出现可写事件时,就说明连接已经建立成功了。 //调用socket,connect连接一个后端的peer,然后设置读写事件回调函数,进入发送数据的ngx_http_upstream_send_request里面 //这里负责连接后端服务,然后设置各个读写事件回调。最后如果连接建立成功,会调用ngx_http_upstream_send_request进行数据发送。 */ static void ngx_http_upstream_connect(ngx_http_request_t *r, ngx_http_upstream_t *u) { ngx_int_t rc; ngx_connection_t *c; r->connection->log->action = "connecting to upstream"; if (u->state && u->state->response_time) { u->state->response_time = ngx_current_msec - u->state->response_time; } 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)); u->state->response_time = ngx_current_msec; u->state->connect_time = (ngx_msec_t) -1; u->state->header_time = (ngx_msec_t) -1; //初始赋值见ngx_http_upstream_connect->ngx_event_connect_peer(&u->peer); //可以看出有多少个客户端连接,nginx就要与php服务器建立多少个连接,为什么nginx和php服务器不只建立一个连接呢???????????????? rc = ngx_event_connect_peer(&u->peer); //建立一个TCP套接字,同时,这个套接字需要设置为非阻塞模式。 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream connect: %i", rc); if (rc == NGX_ERROR) {// //若 rc = NGX_ERROR,表示发起连接失败,则调用ngx_http_upstream_finalize_request 方法关闭连接请求,并 return 从当前函数返回; ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } u->state->peer = u->peer.name; if (rc == NGX_BUSY) { //若 rc = NGX_BUSY,表示当前上游服务器处于不活跃状态,则调用 ngx_http_upstream_next 方法根据传入的参数尝试重新发起连接请求,并 return 从当前函数返回; ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no live upstreams"); ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_NOLIVE); return; } if (rc == NGX_DECLINED) { //若 rc = NGX_DECLINED,表示当前上游服务器负载过重,则调用 ngx_http_upstream_next 方法尝试与其他上游服务器建立连接,并 return 从当前函数返回; ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); return; } /* rc == NGX_OK || rc == NGX_AGAIN || rc == NGX_DONE */ c = u->peer.connection; c->data = r; /* 设置上游连接 ngx_connection_t 结构体的读事件、写事件的回调方法 handler 都为 ngx_http_upstream_handler,设置 ngx_http_upstream_t 结构体的写事件 write_event_handler 的回调为 ngx_http_upstream_send_request_handler,读事件 read_event_handler 的回调方法为 ngx_http_upstream_process_header; */ c->write->handler = ngx_http_upstream_handler; c->read->handler = ngx_http_upstream_handler; //这一步骤实际上决定了向上游服务器发送请求的方法是ngx_http_upstream_send_request_handler. //由写事件(写数据或者客户端连接返回成功)触发c->write->handler = ngx_http_upstream_handler;然后在ngx_http_upstream_handler中执行ngx_http_upstream_send_request_handler u->write_event_handler = ngx_http_upstream_send_request_handler; //如果ngx_event_connect_peer返回NGX_AGAIN也通过该函数触发连接成功 //设置upstream机制的read_event_handler方法为ngx_http_upstream_process_header,也就是由ngx_http_upstream_process_header方法接收上游服务器的响应。 u->read_event_handler = ngx_http_upstream_process_header; c->sendfile &= r->connection->sendfile; u->output.sendfile = c->sendfile; if (c->pool == NULL) { /* we need separate pool here to be able to cache SSL connections */ c->pool = ngx_create_pool(128, r->connection->log); if (c->pool == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } } c->log = r->connection->log; c->pool->log = c->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; u->writer.last = &u->writer.out; u->writer.connection = c; u->writer.limit = 0; 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 && r->request_body->buf && r->request_body->temp_file && r == r->main) //客户端包体存入了临时文件后,则使用r->request_body->bufs链表中的ngx_buf_t结构的file_pos和file_last指向,所以r->request_body->buf可以继续读取包体 { /* * the r->request_body->buf can be reused for one request only, * the subrequests should allocate their own temporary 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; } u->output.free->buf = r->request_body->buf; u->output.free->next = NULL; u->output.allocated = 1; 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) { //这里的定时器在ngx_http_upstream_send_request会删除 /* 2025/04/24 02:54:29[ ngx_event_connect_peer, 32] [debug] 14867#14867: *1 socket 12 2025/04/24 02:54:29[ ngx_epoll_add_connection, 1486] [debug] 14867#14867: *1 epoll add connection: fd:12 ev:80002005 2025/04/24 02:54:29[ ngx_event_connect_peer, 125] [debug] 14867#14867: *1 connect to 127.0.0.1:3666, fd:12 #2 2025/04/24 02:54:29[ ngx_http_upstream_connect, 1549] [debug] 14867#14867: *1 http upstream connect: -2 //返回NGX_AGAIN 2025/04/24 02:54:29[ ngx_event_add_timer, 88] [debug] 14867#14867: *1 <ngx_http_upstream_connect, 1665> event timer add: 12: 60000:1677807811 //这里添加 2025/04/24 02:54:29[ ngx_http_finalize_request, 2526] [debug] 14867#14867: *1 http finalize request: -4, "/test.php?" a:1, c:2 2025/04/24 02:54:29[ ngx_http_close_request, 3789] [debug] 14867#14867: *1 http request count:2 blk:0 2025/04/24 02:54:29[ ngx_worker_process_cycle, 1110] [debug] 14867#14867: worker(14867) cycle again 2025/04/24 02:54:29[ ngx_trylock_accept_mutex, 405] [debug] 14867#14867: accept mutex locked 2025/04/24 02:54:29[ ngx_epoll_process_events, 1614] [debug] 14867#14867: begin to epoll_wait, epoll timer: 60000 2025/04/24 02:54:29[ ngx_epoll_process_events, 1699] [debug] 14867#14867: epoll: fd:11 epoll-out(ev:0004) d:B27440E8 2025/04/24 02:54:29[ ngx_epoll_process_events, 1772] [debug] 14867#14867: *1 post event AEB44068 2025/04/24 02:54:29[ ngx_epoll_process_events, 1699] [debug] 14867#14867: epoll: fd:12 epoll-out(ev:0004) d:B2744158 2025/04/24 02:54:29[ ngx_epoll_process_events, 1772] [debug] 14867#14867: *1 post event AEB44098 2025/04/24 02:54:29[ ngx_process_events_and_timers, 371] [debug] 14867#14867: epoll_wait timer range(delta): 2 2025/04/24 02:54:29[ ngx_event_process_posted, 65] [debug] 14867#14867: posted event AEB44068 2025/04/24 02:54:29[ ngx_event_process_posted, 67] [debug] 14867#14867: *1 delete posted event AEB44068 2025/04/24 02:54:29[ ngx_http_request_handler, 2400] [debug] 14867#14867: *1 http run request: "/test.php?" 2025/04/24 02:54:29[ngx_http_upstream_check_broken_connection, 1335] [debug] 14867#14867: *1 http upstream check client, write event:1, "/test.php" 2025/04/24 02:54:29[ngx_http_upstream_check_broken_connection, 1458] [debug] 14867#14867: *1 http upstream recv(): -1 (11: Resource temporarily unavailable) 2025/04/24 02:54:29[ ngx_event_process_posted, 65] [debug] 14867#14867: posted event AEB44098 2025/04/24 02:54:29[ ngx_event_process_posted, 67] [debug] 14867#14867: *1 delete posted event AEB44098 2025/04/24 02:54:29[ ngx_http_upstream_handler, 1295] [debug] 14867#14867: *1 http upstream request: "/test.php?" 2025/04/24 02:54:29[ngx_http_upstream_send_request_handler, 2210] [debug] 14867#14867: *1 http upstream send request handler 2025/04/24 02:54:29[ ngx_http_upstream_send_request, 2007] [debug] 14867#14867: *1 http upstream send request 2025/04/24 02:54:29[ngx_http_upstream_send_request_body, 2095] [debug] 14867#14867: *1 http upstream send request body 2025/04/24 02:54:29[ ngx_chain_writer, 690] [debug] 14867#14867: *1 chain writer buf fl:0 s:968 2025/04/24 02:54:29[ ngx_chain_writer, 704] [debug] 14867#14867: *1 chain writer in: 080EC838 2025/04/24 02:54:29[ ngx_writev, 192] [debug] 14867#14867: *1 writev: 968 of 968 2025/04/24 02:54:29[ ngx_chain_writer, 740] [debug] 14867#14867: *1 chain writer out: 00000000 2025/04/24 02:54:29[ ngx_event_del_timer, 39] [debug] 14867#14867: *1 <ngx_http_upstream_send_request, 2052> event timer del: 12: 1677807811//这里删除 2025/04/24 02:54:29[ ngx_event_add_timer, 88] [debug] 14867#14867: *1 <ngx_http_upstream_send_request, 2075> event timer add: 12: 60000:1677807813 */ /* 若 rc = NGX_AGAIN,表示当前已经发起连接,但是没有收到上游服务器的确认应答报文,即上游连接的写事件不可写,则需调用 ngx_add_timer 方法将上游连接的写事件添加到定时器中,管理超时确认应答; 这一步处理非阻塞的连接尚未成功建立时的动作。实际上,在ngx_event_connect_peer中,套接字已经加入到epoll中监控了,因此, 这一步将调用ngx_add_timer方法把写事件添加到定时器中,超时时间为ngx_http_upstream_conf_t结构体中的connect_timeout 成员,这是在设置建立TCP连接的超时时间。 */ //这里的定时器在ngx_http_upstream_send_request会删除 ngx_add_timer(c->write, u->conf->connect_timeout, NGX_FUNC_LINE); return; //大部分情况通过这里返回,然后通过ngx_http_upstream_send_request_handler来执行epoll write事件 } //若 rc = NGX_OK,表示成功建立连接,则调用 ngx_http_upsream_send_request 方法向上游服务器发送请求; #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方法向上游服务器发送请求 ngx_http_upstream_send_request(r, u, 1); }
ngx_int_t ngx_event_connect_peer(ngx_peer_connection_t *pc) { int rc; ngx_int_t event; ngx_err_t err; ngx_uint_t level; ngx_socket_t s; ngx_event_t *rev, *wev; ngx_connection_t *c; /*ngx_http_upstream_get_round_robin_peer ngx_http_upstream_get_least_conn_peer ngx_http_upstream_get_hash_peer ngx_http_upstream_get_ip_hash_peer ngx_http_upstream_get_keepalive_peer等 */ rc = pc->get(pc, pc->data);//ngx_http_upstream_get_round_robin_peer获取一个peer if (rc != NGX_OK) {/* 说明后端服务器全部down掉,或者配置的是keepalive方式,直接选择某个peer上面已有的长连接来发送数据,则直接从这里返回 */ return rc; } s = ngx_socket(pc->sockaddr->sa_family, SOCK_STREAM, 0); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, "socket %d", s); if (s == (ngx_socket_t) -1) { ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno, ngx_socket_n " failed"); return NGX_ERROR; } /* 由于Nginx的事件框架要求每个连接都由一个ngx_connection-t结构体来承载,因此这一步将调用ngx_get_connection方法,由ngx_cycle_t 核心结构体中free_connections指向的空闲连接池处获取到一个ngx_connection_t结构体,作为承载Nginx与上游服务器间的TCP连接 */ c = ngx_get_connection(s, pc->log); if (c == NULL) { if (ngx_close_socket(s) == -1) { ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno, ngx_close_socket_n "failed"); } return NGX_ERROR; } if (pc->rcvbuf) { if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (const void *) &pc->rcvbuf, sizeof(int)) == -1) { ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno, "setsockopt(SO_RCVBUF) failed"); goto failed; } } if (ngx_nonblocking(s) == -1) { //建立一个TCP套接字,同时,这个套接字需要设置为非阻塞模式。 ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno, ngx_nonblocking_n " failed"); goto failed; } if (pc->local) { if (bind(s, pc->local->sockaddr, pc->local->socklen) == -1) { ngx_log_error(NGX_LOG_CRIT, pc->log, ngx_socket_errno, "bind(%V) failed", &pc->local->name); goto failed; } } c->recv = ngx_recv; c->send = ngx_send; c->recv_chain = ngx_recv_chain; c->send_chain = ngx_send_chain; /* 和后端的ngx_connection_t在ngx_event_connect_peer这里置为1,但在ngx_http_upstream_connect中c->sendfile &= r->connection->sendfile;, 和客户端浏览器的ngx_connextion_t的sendfile需要在ngx_http_update_location_config中判断,因此最终是由是否在configure的时候是否有加 sendfile选项来决定是置1还是置0 */ c->sendfile = 1; c->log_error = pc->log_error; if (pc->sockaddr->sa_family == AF_UNIX) { c->tcp_nopush = NGX_TCP_NOPUSH_DISABLED; c->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; #if (NGX_SOLARIS) /* Solaris's sendfilev() supports AF_NCA, AF_INET, and AF_INET6 */ c->sendfile = 0; #endif } rev = c->read; wev = c->write; rev->log = pc->log; wev->log = pc->log; pc->connection = c; c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); /* 事件模块的ngx_event_actions接口,其中的add_conn方法可以将TCP套接字以期待可读、可写事件的方式添加到事件搜集器中。对于 epoll事件模块来说,add_conn方法就是把套接字以期待EPOLLIN EPOLLOUT事件的方式加入epoll中,这一步即调用add_conn方法把刚刚 建立的套接字添加到epoll中,表示如果这个套接字上出现了预期的网络事件,则希望epoll能够回调它的handler方法。 */ if (ngx_add_conn) { /* 将这个连接ngx_connection t上的读/写事件的handler回调方法都设置为ngx_http_upstream_handler。,见函数外层的ngx_http_upstream_connect */ if (ngx_add_conn(c) == NGX_ERROR) { goto failed; } } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pc->log, 0, "connect to %V, fd:%d #%uA", pc->name, s, c->number); /* 调用connect方法向上游服务器发起TCP连接,作为非阻塞套接字,connect方法可能立刻返回连接建立成功,也可能告诉用户继续等待上游服务器的响应 对connect连接是否建立成功的检查会在函数外面的u->read_event_handler = ngx_http_upstream_process_header;见函数外层的ngx_http_upstream_connect */ /* 针对非阻塞I/O执行的系统调用则总是立即返回,而不管事件足否已经发生。如果事件没有眭即发生,这些系统调用就 返回—1.和出错的情况一样。此时我们必须根据errno来区分这两种情况。对accept、send和recv而言,事件未发牛时errno 通常被设置成EAGAIN(意为“再来一次”)或者EWOULDBLOCK(意为“期待阻塞”):对conncct而言,errno则被 设置成EINPROGRESS(意为“在处理中")。 */ //connect的时候返回成功后使用的sock就是socket创建的sock,这和服务器端accept成功返回一个新的sock不一样 //上面的ngx_add_conn已经把读写事件一起添加到了epoll中 rc = connect(s, pc->sockaddr, pc->socklen); //connect返回值可以参考<linux高性能服务器开发> 9.5节 if (rc == -1) { err = ngx_socket_errno; if (err != NGX_EINPROGRESS #if (NGX_WIN32) /* Winsock returns WSAEWOULDBLOCK (NGX_EAGAIN) */ && err != NGX_EAGAIN #endif ) { if (err == NGX_ECONNREFUSED #if (NGX_LINUX) /* * Linux returns EAGAIN instead of ECONNREFUSED * for unix sockets if listen queue is full */ || err == NGX_EAGAIN #endif || err == NGX_ECONNRESET || err == NGX_ENETDOWN || err == NGX_ENETUNREACH || err == NGX_EHOSTDOWN || err == NGX_EHOSTUNREACH) { level = NGX_LOG_ERR; } else { level = NGX_LOG_CRIT; } ngx_log_error(level, c->log, err, "connect() to %V failed", pc->name); ngx_close_connection(c); pc->connection = NULL; return NGX_DECLINED; } } if (ngx_add_conn) { if (rc == -1) { //这个表示发出了连接三步握手中的SYN,单还没有等待对方完全应答回来表示连接成功通过外层的 //c->write->handler = ngx_http_upstream_handler; u->write_event_handler = ngx_http_upstream_send_request_handler促发返回成功 /* NGX_EINPROGRESS */ return NGX_AGAIN; } ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pc->log, 0, "connected"); wev->ready = 1; return NGX_OK; } if (ngx_event_flags & NGX_USE_IOCP_EVENT) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, ngx_socket_errno, "connect(): %d", rc); if (ngx_blocking(s) == -1) { ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno, ngx_blocking_n " failed"); goto failed; } /* * FreeBSD's aio allows to post an operation on non-connected socket. * NT does not support it. * * TODO: check in Win32, etc. As workaround we can use NGX_ONESHOT_EVENT */ rev->ready = 1; wev->ready = 1; return NGX_OK; } if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { /* kqueue */ event = NGX_CLEAR_EVENT; } else { /* select, poll, /dev/poll */ event = NGX_LEVEL_EVENT; } char tmpbuf[256]; snprintf(tmpbuf, sizeof(tmpbuf), "<%25s, %5d> epoll NGX_READ_EVENT(et) read add", NGX_FUNC_LINE); ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pc->log, 0, tmpbuf); if (ngx_add_event(rev, NGX_READ_EVENT, event) != NGX_OK) { goto failed; } if (rc == -1) { /* NGX_EINPROGRESS */ char tmpbuf[256]; snprintf(tmpbuf, sizeof(tmpbuf), "<%25s, %5d> epoll NGX_WRITE_EVENT(et) read add", NGX_FUNC_LINE); ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pc->log, 0, tmpbuf); if (ngx_add_event(wev, NGX_WRITE_EVENT, event) != NGX_OK) { goto failed; } return NGX_AGAIN; //这个表示发出了连接三步握手中的SYN,单还没有等待对方完全应答回来表示连接成功 //通过外层的 c->write->handler = ngx_http_upstream_handler; u->write_event_handler = ngx_http_upstream_send_request_handler促发返回成功 } ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pc->log, 0, "connected"); wev->ready = 1; return NGX_OK; failed: ngx_close_connection(c); pc->connection = NULL; return NGX_ERROR; }
connection的读写回调函数——ngx_http_upstream_handler
由于是非阻塞的 socket 调用,因此在调用 connect 建立连接之前,首先将 socket 加入了 epoll 监听,只有当他出现可写事件时,才说明连接建立成功在函数的最后,调用了 ngx_http_upstream_send_request 函数给上游服务器发送请求,执行到此说明链接已经建立成功
/客户端事件处理handler一般(write(read)->handler)一般为ngx_http_request_handler, 和后端的handler一般(write(read)->handler)一般为ngx_http_upstream_handler, 和后端的 //和后端服务器的读写事件触发后走到这里 static void ngx_http_upstream_handler(ngx_event_t *ev) { ngx_connection_t *c; ngx_http_request_t *r; ngx_http_upstream_t *u; int writef = ev->write; c = ev->data; r = c->data; u = r->upstream; c = r->connection; ngx_http_set_log_request(c->log, r); ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream request(ev->write:%d): \"%V?%V\"", writef, &r->uri, &r->args); //当ev为ngx_connection_t->write 默认write为1;当ev为ngx_connection_t->read 默认write为0 if (ev->write) { //说明是c->write事件 u->write_event_handler(r, u);//ngx_http_upstream_send_request_handler } else { //说明是c->read事件 u->read_event_handler(r, u); //ngx_http_upstream_process_header ngx_http_upstream_process_non_buffered_upstream } ngx_http_run_posted_requests(c); }
发送请求到上游服务器
前面在介绍ngx_http_upstream_connect函数时,我们看到将ngx_http_upstream_t中的write_event_handler设置为了ngx_http_upstream_send_request_handler,而ngx_http_upstream_connect的最后直接调用了ngx_http_upstream_send_request发送请求。
//ngx_http_upstream_send_request_handler用户向后端发送包体时,一次发送没完完成,再次出发epoll write的时候调用 static void ngx_http_upstream_send_request_handler(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, r->connection->log, 0, "http upstream send request handler"); //表示向上游服务器发送的请求已经超时 if (c->write->timedout) { //该定时器在ngx_http_upstream_send_request添加的 ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT); return; } #if (NGX_HTTP_SSL) if (u->ssl && c->ssl == NULL) { ngx_http_upstream_ssl_init_connection(r, u, c); return; } #endif //表示上游服务器的响应需要直接转发给客户端,并且此时已经把响应头发送给客户端了 if (u->header_sent) { //都已经收到后端的数据并且发送给客户端浏览器了,说明不会再想后端写数据, u->write_event_handler = ngx_http_upstream_dummy_handler; (void) ngx_handle_write_event(c->write, 0, NGX_FUNC_LINE); return; } ngx_http_upstream_send_request(r, u, 1); } static void ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_uint_t do_write) //向上游服务器发送请求 当一次发送不完,通过ngx_http_upstream_send_request_handler再次触发发送 { ngx_int_t rc; ngx_connection_t *c; c = u->peer.connection; //向上游服务器的连接信息 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream send request"); if (u->state->connect_time == (ngx_msec_t) -1) { u->state->connect_time = ngx_current_msec - u->state->response_time; } //通过getsockopt测试与上游服务器的tcp连接是否异常 if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) { //测试连接失败 ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);//如果测试失败,调用ngx_http_upstream_next函数,这个函数可能再次调用peer.get调用别的连接。 return; } c->log->action = "sending request to upstream"; rc = ngx_http_upstream_send_request_body(r, u, do_write); if (rc == NGX_ERROR) { /* 若返回值rc=NGX_ERROR,表示当前连接上出错, 将错误信息传递给ngx_http_upstream_next方法, 该方法根据错误信息决定 是否重新向上游其他服务器发起连接; 并return从当前函数返回; */ ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); return; } if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { ngx_http_upstream_finalize_request(r, u, rc); return; } /* 若返回值rc = NGX_AGAIN,表示请求数据并未完全发送, 即有剩余的请求数据保存在output中,但此时,写事件已经不可写, 则调用ngx_add_timer方法把当前连接上的写事件添加到定时器机制, 并调用ngx_handle_write_event方法将写事件注册到epoll事件机制中; */ //通过ngx_http_upstream_read_request_handler进行再次epoll write if (rc == NGX_AGAIN) {//协议栈缓冲区已满,需要等待发送数据出去后出发epoll可写,从而继续write if (!c->write->ready) { //这里加定时器的原因是,例如我把数据扔到协议栈了,并且协议栈已经满了,但是对方就是不接受数据,造成数据一直在协议栈缓存中 //因此只要数据发送出去,就会触发epoll继续写,从而在下面两行删除写超时定时器 ngx_add_timer(c->write, u->conf->send_timeout, NGX_FUNC_LINE); //如果超时会执行ngx_http_upstream_send_request_handler,这里面对写超时进行处理 } else if (c->write->timer_set) { //例如ngx_http_upstream_send_request_body发送了三次返回NGX_AGAIN,那么第二次就需要把第一次上面的超时定时器关了,表示发送正常 ngx_del_timer(c->write, NGX_FUNC_LINE); } //在连接后端服务器conncet前,有设置ngx_add_conn,里面已经将fd添加到了读写事件中,因此这里实际上只是简单执行下ngx_send_lowat if (ngx_handle_write_event(c->write, u->conf->send_lowat, NGX_FUNC_LINE) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } return; } /* rc == NGX_OK */ //向后端的数据发送完毕 //当发往后端服务器的数据包过大,需要分多次发送的时候,在上面的if (rc == NGX_AGAIN)中会添加定时器来触发发送,如果协议栈一直不发送数据出去 //就会超时,如果数据最终全部发送出去则需要为最后一次time_write添加删除操作。 //如果发往后端的数据长度后小,则一般不会再上门添加定时器,这里的timer_set肯定为0,所以如果拔掉后端网线,通过ngx_http_upstream_test_connect //是判断不出后端服务器掉线的,上面的ngx_http_upstream_send_request_body还是会返回成功的,所以这里有个bug if (c->write->timer_set) { //这里的定时器是ngx_http_upstream_connect中connect返回NGX_AGAIN的时候添加的定时器 /* 2025/04/24 02:54:29[ ngx_event_connect_peer, 32] [debug] 14867#14867: *1 socket 12 2025/04/24 02:54:29[ ngx_epoll_add_connection, 1486] [debug] 14867#14867: *1 epoll add connection: fd:12 ev:80002005 2025/04/24 02:54:29[ ngx_event_connect_peer, 125] [debug] 14867#14867: *1 connect to 127.0.0.1:3666, fd:12 #2 2025/04/24 02:54:29[ ngx_http_upstream_connect, 1549] [debug] 14867#14867: *1 http upstream connect: -2 //返回NGX_AGAIN 2025/04/24 02:54:29[ ngx_event_add_timer, 88] [debug] 14867#14867: *1 <ngx_http_upstream_connect, 1665> event timer add: 12: 60000:1677807811 //这里添加 2025/04/24 02:54:29[ ngx_http_finalize_request, 2526] [debug] 14867#14867: *1 http finalize request: -4, "/test.php?" a:1, c:2 2025/04/24 02:54:29[ ngx_http_close_request, 3789] [debug] 14867#14867: *1 http request count:2 blk:0 2025/04/24 02:54:29[ ngx_worker_process_cycle, 1110] [debug] 14867#14867: worker(14867) cycle again 2025/04/24 02:54:29[ ngx_trylock_accept_mutex, 405] [debug] 14867#14867: accept mutex locked 2025/04/24 02:54:29[ ngx_epoll_process_events, 1614] [debug] 14867#14867: begin to epoll_wait, epoll timer: 60000 2025/04/24 02:54:29[ ngx_epoll_process_events, 1699] [debug] 14867#14867: epoll: fd:11 epoll-out(ev:0004) d:B27440E8 2025/04/24 02:54:29[ ngx_epoll_process_events, 1772] [debug] 14867#14867: *1 post event AEB44068 2025/04/24 02:54:29[ ngx_epoll_process_events, 1699] [debug] 14867#14867: epoll: fd:12 epoll-out(ev:0004) d:B2744158 2025/04/24 02:54:29[ ngx_epoll_process_events, 1772] [debug] 14867#14867: *1 post event AEB44098 2025/04/24 02:54:29[ ngx_process_events_and_timers, 371] [debug] 14867#14867: epoll_wait timer range(delta): 2 2025/04/24 02:54:29[ ngx_event_process_posted, 65] [debug] 14867#14867: posted event AEB44068 2025/04/24 02:54:29[ ngx_event_process_posted, 67] [debug] 14867#14867: *1 delete posted event AEB44068 2025/04/24 02:54:29[ ngx_http_request_handler, 2400] [debug] 14867#14867: *1 http run request: "/test.php?" 2025/04/24 02:54:29[ngx_http_upstream_check_broken_connection, 1335] [debug] 14867#14867: *1 http upstream check client, write event:1, "/test.php" 2025/04/24 02:54:29[ngx_http_upstream_check_broken_connection, 1458] [debug] 14867#14867: *1 http upstream recv(): -1 (11: Resource temporarily unavailable) 2025/04/24 02:54:29[ ngx_event_process_posted, 65] [debug] 14867#14867: posted event AEB44098 2025/04/24 02:54:29[ ngx_event_process_posted, 67] [debug] 14867#14867: *1 delete posted event AEB44098 2025/04/24 02:54:29[ ngx_http_upstream_handler, 1295] [debug] 14867#14867: *1 http upstream request: "/test.php?" 2025/04/24 02:54:29[ngx_http_upstream_send_request_handler, 2210] [debug] 14867#14867: *1 http upstream send request handler 2025/04/24 02:54:29[ ngx_http_upstream_send_request, 2007] [debug] 14867#14867: *1 http upstream send request 2025/04/24 02:54:29[ngx_http_upstream_send_request_body, 2095] [debug] 14867#14867: *1 http upstream send request body 2025/04/24 02:54:29[ ngx_chain_writer, 690] [debug] 14867#14867: *1 chain writer buf fl:0 s:968 2025/04/24 02:54:29[ ngx_chain_writer, 704] [debug] 14867#14867: *1 chain writer in: 080EC838 2025/04/24 02:54:29[ ngx_writev, 192] [debug] 14867#14867: *1 writev: 968 of 968 2025/04/24 02:54:29[ ngx_chain_writer, 740] [debug] 14867#14867: *1 chain writer out: 00000000 2025/04/24 02:54:29[ ngx_event_del_timer, 39] [debug] 14867#14867: *1 <ngx_http_upstream_send_request, 2052> event timer del: 12: 1677807811//这里删除 2025/04/24 02:54:29[ ngx_event_add_timer, 88] [debug] 14867#14867: *1 <ngx_http_upstream_send_request, 2075> event timer add: 12: 60000:1677807813 */ ngx_del_timer(c->write, NGX_FUNC_LINE); } /* 若返回值 rc = NGX_OK,表示已经发送完全部请求数据, 准备接收来自上游服务器的响应报文,则执行以下程序; */ if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) { if (ngx_tcp_push(c->fd) == NGX_ERROR) { ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno, ngx_tcp_push_n " failed"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; } u->write_event_handler = ngx_http_upstream_dummy_handler; //数据已经在前面全部发往后端服务器了,所以不需要再做写处理 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "send out chain data to uppeer server OK"); //在连接后端服务器conncet前,有设置ngx_add_conn,里面已经将fd添加到了读写事件中,因此这里实际上只是简单执行下ngx_send_lowat if (ngx_handle_write_event(c->write, 0, NGX_FUNC_LINE) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } //这回数据已经发送了,可以准备接收了,设置接收后端应答的超时定时器。 /* 该定时器在收到后端应答数据后删除,见ngx_event_pipe if (rev->timer_set) { ngx_del_timer(rev, NGX_FUNC_LINE); } */ ngx_add_timer(c->read, u->conf->read_timeout, NGX_FUNC_LINE); //如果超时在该函数检测ngx_http_upstream_process_header if (c->read->ready) { ngx_http_upstream_process_header(r, u); return; } }
接收上游服务器的响应
Nginx的upstream机制支持三种响应包体的处理方式:不转发响应、转发响应时以下游网速优先、转发响应时以上游网速优先。当ngx_http_request_t结构体的subrequest_in_memory标志位为1时,不转发包体到下游,由 http 模块实现的 input_filter 方法处理请求包体;
当subrequest_in_memory为0时,直接转发响应包体;
而ngx_http_upstream_conf_t配置结构中的buffering为0时,则以下游网速优先,即使用固定大小的内存作为缓存;
当buffering为1时,则以上游网速优先,即采用更多的内存、硬盘文件作为缓存。
接收、解析响应头部的ngx_http_upstream_process_header方法:
//ngx_http_upstream_handler中执行 /* 后端发送过来的头部行包体格式: 8字节fastcgi头部行+ 数据(头部行信息+ 空行 + 实际需要发送的包体内容) + 填充字段 */ static void ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) {//读取FCGI头部数据,或者proxy头部数据。ngx_http_upstream_send_request发送完数据后, //会调用这里,或者有可写事件的时候会调用这里。 //ngx_http_upstream_connect函数连接fastcgi后,会设置这个回调函数为fcgi连接的可读事件回调。 ssize_t n; ngx_int_t rc; ngx_connection_t *c; c = u->peer.connection; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream process header, fd:%d, buffer_size:%uz", c->fd, u->conf->buffer_size); c->log->action = "reading response header from upstream"; if (c->read->timedout) {//读超时了,轮询下一个。 ngx_event_expire_timers超时后走到这里 //该定时器添加地方在ngx_http_upstream_send_request ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT); return; } // request_sent为1则代表已经向上游发过请求;为0则代表还没有发送请求,没有发送请求却收到上游的响应时,则不符合逻辑,进行下一步动作 // ngx_http_upstream_next会根据配置信息决定是否直接结束请求,还是寻找下一个上游服务器 if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); return; } // 检查用于接收上游响应的buffer,当start为NULL时,代表该缓冲区尚未进行分配,此时会按照配置指定的buffer_size进行缓冲区的分配 if (u->buffer.start == NULL) { //分配一块缓存,用来存放接受回来的数据。 u->buffer.start = ngx_palloc(r->pool, u->conf->buffer_size); //头部行部分(也就是第一个fastcgi data标识信息,里面也会携带一部分网页数据)的fastcgi标识信息开辟的空间用buffer_size配置指定 if (u->buffer.start == NULL) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } u->buffer.pos = u->buffer.start; u->buffer.last = u->buffer.start; u->buffer.end = u->buffer.start + u->conf->buffer_size; u->buffer.temporary = 1; u->buffer.tag = u->output.tag; //初始化headers_in存放头部信息,后端FCGI,proxy解析后的HTTP头部将放入这里 if (ngx_list_init(&u->headers_in.headers, r->pool, 8, sizeof(ngx_table_elt_t)) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } #if (NGX_HTTP_CACHE) /* pVpVZ" KEY: /test.php //下面是后端实际返回的内容,上面的是预留的头部 IX-Powered-By: PHP/5.2.13 Content-type: text/html <Html> <Head> <title>Your page Subject and domain name</title> */ if (r->cache) { //注意这里跳过了预留的头部内存,用于存储cache写入文件时候的头部部分,见 u->buffer.pos += r->cache->header_start; u->buffer.last = u->buffer.pos; } #endif } for ( ;; ) { // 读取响应的内容存储在buffer中,每次读取的最大不超过buffer_size,即当前缓冲区的剩余空间大小 //recv 为 ngx_unix_recv,读取数据放在u->buffer.last的位置,返回读到的大小。 n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last); if (n == NGX_AGAIN) { //内核缓冲区已经没数据了 #if 0 ngx_add_timer(rev, u->read_timeout); #endif if (ngx_handle_read_event(c->read, 0, NGX_FUNC_LINE) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } return; } if (n == 0) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "upstream prematurely closed connection"); } // 读取出错或者连接已关闭,则调用next函数决定是终止连接,还是重新选择上游服务器 if (n == NGX_ERROR || n == 0) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); return; } // n 大于0时,代表读取到的数据,此时last游标需要往后移动n个字节,last初始化时与start相同,指向buffer起始地址 u->buffer.last += n; #if 0 u->valid_header_in = 0; u->peer.cached = 0; #endif //ngx_http_xxx_process_header ngx_http_proxy_process_header // 开始处理读取到的响应头部信息 rc = u->process_header(r);//ngx_http_fastcgi_process_header等,进行数据处理,比如后端返回的数据头部解析,body读取等。 // 检查process_header的返回值,当返回NGX_AGAIN时,需要判断一下是否当前的缓冲区已经被用尽,如果被用尽说明一个buffer_size无法容纳整个响应头 if (rc == NGX_AGAIN) { ngx_log_debugall(c->log, 0, " ngx_http_upstream_process_header u->process_header() return NGX_AGAIN"); // 当buffer无法容纳整个响应头部时,调用next决定是终止连接还是选择下一个上游服务器 if (u->buffer.last == u->buffer.end) { //分配的用来存储fastcgi STDOUT头部行包体的buf已经用完了头部行都还没有解析完成, ngx_log_error(NGX_LOG_ERR, c->log, 0, "upstream sent too big header"); ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER); return; } continue; } break; } // 当process_header处理的是完整的响应头部时,会进一步判断其返回值,检测到无效的响应头部时,进行next的进一步决策处理 if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER); return; } if (rc == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } // 走到目前位置,当前的process_header至少是执行成功了,完整的解析了响应的头部信息 /* rc == NGX_OK */ u->state->header_time = ngx_current_msec - u->state->response_time; if (u->headers_in.status_n >= NGX_HTTP_SPECIAL_RESPONSE) { if (ngx_http_upstream_test_next(r, u) == NGX_OK) { return; } if (ngx_http_upstream_intercept_errors(r, u) == NGX_OK) { return; } } // 处理已经解析出的头部,该函数会把已经解析出的头部,设置到ngx_http_request_t结构体的headers_out成员中 // 当调用ngx_http_send_header时,可以将设置到headers_out中的响应头部发送给客户端 //到这里,FCGI等格式的数据已经解析为标准HTTP的表示形式了(除了BODY),所以可以进行upstream的process_headers。 //上面的 u->process_header(r)已经进行FCGI等格式的解析了。下面将头部数据拷贝到headers_out.headers数组中。 if (ngx_http_upstream_process_headers(r, u) != NGX_OK) { return; } // subrequest_in_memory字段为0时,表示需要转发响应到客户端; if (!r->subrequest_in_memory) {//如果没有子请求了,那就直接发送响应给客户端吧。 //buffering方式和非buffering方式在函数ngx_http_upstream_send_response分叉 ngx_http_upstream_send_response(r, u);//给客户端发送响应,里面会处理header,body分开发送的情况的 return; } /* subrequest content in memory */ //子请求,并且后端数据需要保存到内存在 //注意下面只是把后端数据存到buf中,但是没有发送到客户端,实际发送一般是由ngx_http_finalize_request->ngx_http_set_write_handler实现 if (u->input_filter == NULL) { u->input_filter_init = ngx_http_upstream_non_buffered_filter_init; u->input_filter = ngx_http_upstream_non_buffered_filter; u->input_filter_ctx = r; } if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } n = u->buffer.last - u->buffer.pos; if (n) { u->buffer.last = u->buffer.pos; u->state->response_length += n; if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_ERROR); return; } } if (u->length == 0) { ngx_http_upstream_finalize_request(r, u, 0); return; } u->read_event_handler = ngx_http_upstream_process_body_in_memory;//设置body部分的读事件回调。 ngx_http_upstream_process_body_in_memory(r, u); }
copy https://blog.csdn.net/ai2000ai/article/details/84992300
ngx_http_upstream_create
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!