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.5.2 如何转发多个子请求的响应包体 - 图1

图 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主循环。