5.5.2 如何转发多个子请求的响应包体
ngx_http_postpone_filter_module过滤模块实际上是为了subrequest功能而建立的,本章的例子虽然没有用到postpone(能够应用到的场合其实非常少),这里还是要介绍一下这个过滤模块希望解决什么样的问题,这样读者会对postpone模块和subrequest间的关系有更深刻的了解。
当派生一个子请求访问第三方服务时,如果只是希望接收到完整的响应后在Nginx中解析、处理,那么这里就不需要postpone模块,就像5.6节中的例子那样处理即可;如果原始请求派生出许多子请求,并且希望将所有子请求的响应依次转发给客户端,当然,这里的“依次”就是按照创建子请求的顺序来发送响应,这时,postpone模块就有了“用武之地”。Nginx中的所有请求都是异步执行的,后创建的子请求可能优先执行,这样转发到客户端的响应就会产生混乱。而postpone模块会强制地把待转发的响应包体放在一个链表中发送,只有优先转发的子请求结束后才会开始转发下一个子请求中的响应。下面介绍一下它是如何实现的。
每个请求的ngx_http_request_t结构体中都有一个postponed成员:
struct ngx_http_request_s{
……
ngx_http_postponed_request_t
*postponed;
……
}
它实际上是一个链表:
typedef struct ngx_http_postponed_request_s ngx_http_postponed_request_t;
struct ngx_http_postponed_request_s{
ngx_http_request_t
*request;
ngx_chain_t
*out;
ngx_http_postponed_request_t
*next;
};
从上述代码可以看出,多个ngx_http_postponed_request_t之间使用next指针连接成一个单向链表。ngx_http_postponed_request_t中的out成员是ngx_chain_t结构,它指向的是来自上游的、将要转发给下游的响应包体。
每当使用ngx_http_output_filter方法(反向代理模块也使用该方法转发响应)向下游的客户端发送响应包体时,都会调用到ngx_http_postpone_filter_module过滤模块处理这段要发送的包体。下面看一下过滤包体的ngx_http_postpone_filter方法(在阅读完第11章后再回头看这段代码,概念可能会更加清晰):
//这里的参数in就是将要发送给客户端的一段包体,第6章会详述HTTP过滤模块
static ngx_int_t
ngx_http_postpone_filter(ngx_http_request_tr,ngx_chain_tin)
{
ngx_connection_t
*c;
ngx_http_postponed_request_t*pr;
//c是Nginx与下游客户端间的连接,c->data保存的是原始请求
c=r->connection;
//如果当前请求r是一个子请求(因为c->data指向原始请求)
if(r!=c->data){
/如果待发送的in包体不为空,则把in加到postponed链表中属于当前请求的ngx_http_postponed_request_t结构体的out链表中,同时返回NGX_OK,这意味着本次不会把in包体发给客户端/
if(in){
ngx_http_postpone_filter_add(r,in);
return NGX_OK;
}
//如果当前请求是子请求,而in包体又为空,那么直接返回即可
return NGX_OK;
}
//如果postponed为空,表示请求r没有子请求产生的响应需要转发
if(r->postponed==NULL){
/直接调用下一个HTTP过滤模块继续处理in包体即可。如果没有错误的话,就会开始向下游客户端发送响应/
if(in||c->buffered){
return ngx_http_next_filter(r->main,in);
}
return NGX_OK;
}
/至此,说明postponed链表中是有子请求产生的响应需要转发的,可以先把in包体加到待转发响应的末尾/
if(in){
ngx_http_postpone_filter_add(r,in);
}
//循环处理postponed链表中所有子请求待转发的包体
do{
pr=r->postponed;
/如果pr->request是子请求,则加入到原始请求的posted_requests队列中,等待HTTP框架下次调用这个请求时再来处理(参见11.7节)/
if(pr->request){
r->postponed=pr->next;
c->data=pr->request;
return ngx_http_post_request(pr->request,NULL);
}
//调用下一个HTTP过滤模块转发out链表中保存的待转发的包体
if(pr->out==NULL){
}else{
if(ngx_http_next_filter(r->main,pr->out)==NGX_ERROR){
return NGX_ERROR;
}
}
//遍历完postponed链表
r->postponed=pr->next;
}while(r->postponed);
return NGX_OK;
}
图5-7展示了使用反向代理模块转发子请求的包体的一般流程,其中的第5步正是上面介绍的ngx_http_postpone_filter方法。
图 5-7 子请求转发HTTP包体过程的序列图
下面简单地介绍一下图5-7中的每一个步骤:
1)Nginx主循环中会定期地调用事件模块,检查是否有网络事件发生。
2)事件模块发现这个请求的回调方法属于反向代理模块的接收HTTP包体阶段,于是交由反向代理模块来处理。
3)读取上游服务器发来的包体。
4)对于接收到的字符流,会依次调用所有的HTTP过滤器模块来转发包体。其中,还会调用到postpone过滤模块,这个模块将会处理设置在子请求中的ngx_http_postponed_request_t链表。
5)postpone模块使用ngx_http_postpone_filter方法将待转发的包体以合适的顺序再进行整理发送到下游客户端。如果ngx_http_postpone_filter方法没有通过ngx_http_next_filter方法继续调用其他HTTP过滤模块(如由于顺序的原因而暂停转发某个子请求的响应包体),将会直接跳到第7步,否则继续处理这段接收到的包体(第6步)。
6)继续调用其他HTTP过滤模块,待所有的过滤模块执行完毕后将控制权交还给反向代理模块。
7)当第2步中的网络读取事件处理完毕后,交还控制权给事件模块。
8)当本轮网络事件处理完毕后,交还控制权给Nginx主循环。