12.6 不转发响应时的处理流程

实际上,这里的不转发响应只是不使用upstream机制的转发响应功能而已,但如果HTTP模块有意愿转发响应到下游,还是可以通过input_filter方法实现相关功能的。

当请求属于subrequest子请求,且要求在内存中处理包体时(在第5章介绍过ngx_http_subrequest方法,通过它派生子请求时,可以将最后一个flag参数设置为NGX_HTTP_SUBREQUEST_IN_MEMORY宏,这样就将ngx_http_request_t结构体中的subrequest_in_memory标志位设为1了),就会进入本节描述的不转发响应这个流程。或者通过主动设置subrequest_in_memory标志位为1也可以做到,当然并不推荐这样做。为什么呢?因为不需要转发响应时的应用场景通常如下:业务需求导致需要综合上游服务器的数据来重新构造发往客户端的响应,如从上游的数据库或者Tomcat服务器中获取用户权限信息等。这时,根据Nginx推荐的设计模式,应当由原始请求处理客户端的请求,并派生出子请求访问上游服务器,在这种场景下,一般会希望在内存中解析上游服务器的响应。

注意 其实,在内存中处理上游响应的包体也有两种方式,第一种方式接收到全部的包体后再开始处理,第二种方式是每接收到一部分响应后就处理这一部分。第一种方式可能浪费大量内存用于接收完整的响应包体,第二种方式则会始终复用同一块内存缓冲区。HTTP模块可以自由地选择使用哪种方式。

ngx_http_upstream_process_body_in_memory就是在upstream机制不转发响应时,作为读事件的回调方法在内存中处理上游服务器响应包体的。每次与上游的TCP连接上有读事件触发时,它都会被调用,HTTP模块通过重新实现input_filter方法来处理包体,在12.6.1节中会讨论如何实现这个回调方法;如果HTTP模块不实现input_filter方法,那么upstream机制就会自动使用默认的ngx_http_upstream_non_buffered_filter方法来处理包体,在12.6.2节中会讨论这个默认的input_filter方法做了些什么;在12.6.3节中将会具体分析ngx_http_upstream_process_body_in_memory方法的工作流程。

12.6.1 input_filter方法的设计

先来看一下input_filter回调方法的定义,如下所示。


ngx_int_t(input_filter)(voiddata,ssize_t bytes);


其中,bytes参数是本次接收到的包体长度。而data参数却不是指向接收到的包体的,它实际上是在启动upstream机制之前,所设置的ngx_http_upstream_t结构体中的input_filter_ctx成员,下面看一下它的定义。


void

*input_filter_ctx;


它被设计为可以指向任意结构体,其实就是用来传递参数的。因为在内存中处理包体时,可能需要一个结构体作为上下文存储状态、结果等一些信息,这个结构体必须在启动upstream机制前设置。同时,在处理包体前,还会调用一次input_filter_init方法(HTTP模块如果需要在开始接收包体时初始化变量,都会在这个方法中实现),下面看一下它的定义。


ngx_int_t

input_filter_init)(voiddata);


data参数意义同上,仍然是input_filter_ctx成员。

下面将重点讨论如何在input_filter方法中处理包体。首先要弄清楚是从哪里获取到本次接收到的上游响应包体。答案是可由ngx_buf_t类型的buffer缓冲区获得。buffer缓冲区中的last成员指向本次接收到的包体的起始地址,而input_filter方法的bytes参数表明了本次接收到包体的字节数。通过buffer->last和bytes获取到本次接收到的包体后,下面的工作就是由HTTP模块处理接收到的包体。

在处理完这一次收到的包体后,需要告诉buffer缓冲区已经处理过刚接收到的包体吗?这就需要看业务需求了。

如果我们需要反复使用buffer缓冲区,即buffer指向的这块内存需要复用,或者换句话说,下次接收到的响应将会覆盖buffer上刚刚接收到的响应,那么input_filter方法被调用时必须处理完buffer缓冲区中的全部内容,这种情况下不需要修改buffer缓冲区中的成员。当再次接收到后续的包体时,将会继续从buffer->last指向的内存地址处覆盖上次的包体内容。

如果我们希望buffer缓冲区保存部分或者全部的包体,则需要进行针对性的处理。我们知道,在ngx_buf_t表示的缓冲区中,start和end成员圈定了缓冲区的可用内存,这对于buffer缓冲区来说同样成立,last成员将指向接收到的上游服务器的响应包体的起始内存地址。因此,自由地移动last指针就是在改变buffer缓冲区。例如,如果希望buffer缓冲区存储全部包体内容,那么不妨把last指针向后移动bytes字节(参见12.6.2节);如果希望buffer缓冲区尽可能地接收包体,等缓冲区满后再从头接收,那么可以检测last指针,在last未达到end指针的位置时可以继续向后移动,直到last到达end指针处,在到达end指针后可以把last指针指向start成员,这样又会重头复用这块内存了。

input_filter的返回值非常简单,只要不是返回NGX_ERROR,就都认为是成功的,当然,不出错时最好还是返回NGX_OK。如果返回NGX_ERROR,则请求会结束,参见图12-6。