11.7 subrequest与post请求

从11.6节中可以看到,HTTP框架无论是调用ngx_http_process_request方法(首次从业务上处理请求)还是ngx_http_request_handler方法(TCP连接上后续的事件触发时)处理请求,最后都有一个步骤,就是调用ngx_http_run_posted_requests方法处理post请求(如图11-5中的第8步、图11-7中的第4步)。那么,什么是post请求?为什么要定义post请求?post请求又是怎样实现于HTTP框架中的呢?本节内容将回答这3个问题。

Nginx使用的完全无阻塞的事件驱动框架是难以编写功能复杂的模块的,可以想见,一个请求在处理一个TCP连接时,将需要处理这个连接上的可读、可写以及定时器事件,而可读事件中又包含连接建立成功、连接关闭事件,正常的可读事件在接收到HTTP的不同部分时又要做不同的处理,这就比较复杂了。如果一个请求同时需要与多个上游服务器打交道,同时处理多个TCP连接,那么它需要处理的事件就太多了,这种复杂度会使得模块难以维护。Nginx解决这个问题的手段就是第5章中介绍过的subrequest机制。

subrequest机制有以下两个特点:

❑从业务上把一个复杂的请求拆分成多个子请求,由这些子请求共同合作完成实际的用户请求。

❑每一个HTTP模块通常只需要关心一个请求,而不用试图掌握派生出的所有子请求,这极大地降低了模块的开发复杂度。

这两个特点使得用户可以通过开发多个功能相对单一独立的模块,来共同完成复杂的业务。

post请求的设计就是用于实现subrequest子请求机制的,如果一个请求具备了post请求,并且HTTP框架保证post请求可以在当前请求执行完毕后获得执行机会,那么subrequest功能就可以实现了。子请求的设计在数据结构上是通过ngx_http_request_t结构体的3个成员(posted_requests、parent、main)来保证的。下面看一下表示单向链表的posted_requests成员,它的类型是ngx_http_posted_request_t结构体,如下所示。


typedef struct ngx_http_posted_request_s ngx_http_posted_request_t;

struct ngx_http_posted_request_s{

//指向当前待处理子请求的ngx_http_request_t结构体

ngx_http_request_t*request;

//指向下一个子请求,如果没有,则为NULL空指针

ngx_http_posted_request_t*next;

};


这样,通过posted_requests就把各个子请求以单向链表的数据结构形式组织起来了。

ngx_http_request_t结构体中的parent指向了当前子请求的父请求,这为子请求向前寻找父请求提供了可能性。

ngx_http_request_t结构体中的main成员始终指向一系列有亲缘关系的请求中的唯一的那个原始请求。我们可以在任何一个子请求中通过main成员找到原始请求,而无论怎样执行子请求,都是围绕着main指向的原始请求进行的,在图11-12中可以看到。

11.7 subrequest与post请求 - 图1

图 11-12 post请求的执行

ngx_http_request_t结构体中的count成员将作为引用计数,每当派生出子请求时,原始请求的count成员都会加1,在真正销毁请求前,可以通过检查count成员是否为0以确认是否销毁原始请求,这样可以做到唯有所有的子请求都结束时,原始请求才会销毁,内存池、TCP连接等资源才会释放。

对于subrequest子请求的用法,可参见5.4节,这里不再赘述。图11-12展示ngx_http_run_posted_requests方法是怎么执行一个请求的post请求的,也就是如果一个请求拥有子请求时,子请求是怎么被调度的。

从图11-12中可以看到,在执行某一个请求时,它的所有post请求都可能被执行一遍。下面详细介绍以上流程。

1)首先检查连接是否已销毁,如果连接被销毁,就结束ngx_http_run_posted_requests方法,否则根据ngx_http_request_t结构体中的main成员找到原始请求,这个原始请求的posted_requests成员指向待处理的post请求组成的单链表,如果posted_requests指向NULL空指针,则结束ngx_http_run_posted_requests方法,否则取出链表中首个指向post请求的指针,并跳到第2步执行。

2)将原始请求的posted_requests指针指向链表中下一个post请求(通过第1个post请求的next指针可以获得),当然,下一个post请求有可能不存在,这在下一次循环中就会检测到。

3)调用这个post请求ngx_http_request_t结构体中的write_event_handler方法。为什么不是执行read_event_handler方法呢?原因很简单,子请求不是被网络事件驱动的,因此,执行post请求时就相当于有可写事件,由Nginx主动做出动作。

在本节可以看到,HTTP框架在处理一个请求时,如果发现其有子请求则一定会处理。通过修改原始请求的posted_requests指针,甚至还可以控制从哪一个子请求开始执行,当然,直接修改HTTP框架中的成员很容易出错,一定要慎重。