12.5.3 接收响应头部的流程

下面开始介绍读取上游服务器响应的ngx_http_upstream_process_header方法,这个方法主要用于接收、解析响应头部,当然,由于upstream机制是不涉及应用层协议的,谁使用了upstream谁就要负责解析应用层协议,所以必须由HTTP模块实现的process_header方法解析响应包头。当包头接收、解析完毕后,ngx_http_upstream_process_header方法还会决定以哪种方式处理包体(参见12.5.2节中介绍的3种包体处理方式)。

在接收响应包头的阶段中,处理连接读事件的方法始终是ngx_http_upstream_process_header,也就是说,该方法会反复被调用,在研究其流程时需要特别注意。图12-5描述了它的主要流程。

12.5.3 接收响应头部的流程 - 图1

图 12-5 ngx_http_upstream_process_header方法的流程图

下面详细介绍图12-5中的13个步骤。

1)首先检查读事件是否有效,包括检查timedout标志位是否为1,如果timedout为1,则表示读取响应已经超时,这时跳到第2步调用ngx_http_upstream_next方法决定下一步的动作,其中传递的参数是NGX_HTTP_UPSTREAM_FT_TIMEOUT。如果timedout为0,则继续检查request_sent标志位。如果request_sent为0,则表示还没有发送请求到上游服务器就收到来自上游的响应,不符合upstream的设计场景,这时仍然跳到第2步调用ngx_http_upstream_next方法,传递的参数是NGX_HTTP_UPSTREAM_FT_ERROR。如果读事件完全有效,则跳到第3步执行。

2)只有请求触发了失败条件后,才会执行ngx_http_upstream_next方法,该方法将会根据配置信息决定下一步究竟是重新发起upstream请求,还是结束当前请求,在12.9.2节会详细说明该方法的工作流程。当前读事件处理完毕。

3)检查ngx_http_upstream_t结构体中接收响应头部的buffer缓冲区,如果它的start成员指向NULL,说明缓冲区还未分配内存,这时将按照ngx_http_upstream_conf_t配置结构体中的buffer_size成员指定的大小来为buffer缓冲区分配内存。

4)调用recv方法在buffer缓冲区中读取上游服务器发来的响应。检测recv方法的返回值,有3类返回值会导致3种不同的结果:如果返回NGX_AGAIN,则表示还需要继续接收响应,这时跳到第5步执行;如果返回0(表示上游服务器主动关闭连接)或者返回NGX_ERROR,这时跳到第2步执行ngx_http_upstream_next方法,传递的参数是NGX_HTTP_UPSTREAM_FT_ERROR;如果返回正数,这时该数值表示接收到的响应长度,跳到第6步处理响应。

5)调用ngx_handle_read_event方法将读事件再添加到epoll中,等待读事件的下次触发。ngx_http_upstream_process_header方法执行完毕。

6)调用HTTP模块实现的process_header方法解析响应头部,检测其返回值:返回NGX_HTTP_UPSTREAM_INVALID_HEADER表示包头不合法,这时跳到第2步调用ngx_http_upstream_next方法,传递的参数是NGX_HTTP_UPSTREAM_FT_INVALID_HEADER;返回NGX_ERROR表示出现错误,直接跳到第7步执行;返回NGX_OK表示解析到完整的包头,这时跳到第8步执行;返回NGX_AGAIN表示包头还没有接收完整,这时将检测buffer缓冲区是否用尽,如果缓冲区已经用尽,则说明包头太大了,超出了缓冲区允许的大小,这时跳到第2步调用ngx_http_upstream_next方法,传递的参数依然是NGX_HTTP_UPSTREAM_FT_INVALID_HEADER,其表示包头不合法,而如果缓冲区还有空闲空间,则返回第4步继续接收上游服务器的响应。

7)调用ngx_http_upstream_finalize_request方法结束请求(详见12.9.3节),ngx_http_upstream_process_header方法执行完毕。

8)调用ngx_http_upstream_process_headers方法处理已经解析出的头部,该方法将会把已经解析出的头部设置到请求ngx_http_request_t结构体的headers_out成员中,这样在调用ngx_http_send_header方法发送响应包头给客户端时将会发送这些设置了的头部。

接下来检查是否需要转发响应,ngx_http_request_t结构体中的subrequest_in_memory标志位为1时表示不需要转发响应,跳到第10步执行;subrequest_in_memory为0时表示需要转发响应到客户端,跳到第9步执行。

9)调用ngx_http_upstream_send_response方法开始转发响应给客户端,同时ngx_http_upstream_process_header方法执行完毕。

10)首先检查HTTP模块是否实现了用于处理包体的input_filter方法,如果没有实现,则使用upstream定义的默认方法ngx_http_upstream_non_buffered_filter代替input_filter,其中input_filter_ctx将会被设置为ngx_http_request_t结构体的指针。如果用户已经实现了input_filter方法,则表示用户希望自己处理包体(如ngx_http_memcached_module模块),这时首先调用input_filter_init方法为处理包体做初始化工作。

11)在第6步的process_header方法中,如果解析完包头后缓冲区中还有多余的字符,则表示还接收到了包体,这时将调用input_filter方法第一次处理接收到的包体。

12)设置upstream的read_event_handler为ngx_http_upstream_process_body_in_memory方法,这也表示再有上游服务器发来响应包体,将由该方法来处理(参见12.6节)。

13)调用ngx_http_upstream_process_body_in_memory方法开始处理包体。

从上面的第12步可以看出,当不需要转发响应时,ngx_http_upstream_process_body_in_memory方法将作为读取上游服务器包体的回调方法。什么时候无须转发包体呢?在subrequest_in_memory标志位为1时,实际上,这也意味着当前请求是个subrequest子请求。也就是说,在通常情况下,如果来自客户端的请求直接使用upstream机制,那都需要将上游服务器的响应直接转发给客户端,而如果是客户端请求派生出的子请求,则不需要转发上游的响应。因此,当我们开发HTTP模块实现某个功能时,若需要访问上游服务器获取一些数据,那么可开发两个HTTP模块,第一个HTTP模块用于处理客户端请求,当它需要访问上游服务器时就派生出子请求访问,第二个HTTP模块则专用于访问上游服务器,在子请求解析完上游服务器的响应后,再激活父请求处理客户端要求的业务。

注意 以上描述的开发场景是Nginx推荐用户使用的方式,虽然可以通过任意地修改subrequest标志位来更改以上特性,但目前这种设计对于分离关注点还是非常有效的,是一种很好的设计模式,如无必要最好不要更改。

从上面的第9步可以看出,当需要转发包体时将调用ngx_http_upstream_send_response方法来转发包体。ngx_http_upstream_send_response方法将会根据ngx_http_upstream_conf_t配置结构体中的buffering标志位来决定是否打开缓存来处理响应,也就是说,buffering为0时通常会默认下游网速更快,这时不需要缓存响应(在12.7节中将会介绍这一流程)。如果buffering为1,则表示上游网速更快,这时需要用大量内存、磁盘文件来缓存来自上游的响应(在12.8节中会介绍这一流程)。