大家都知道nginx里面有一个subrequest的概念,也就是子请求,它并不是http标准里面的概念,它是在当前请求中发起的一个新的请求,它拥有自己的ngx_http_request_t结构,uri和args。一般来说使用subrequest的效率可能会有些影响,因为它需要重新从server rewrite开始走一遍request处理的PHASE,但是它在某些情况下使用能给我们带来方便,现在我们比较常用的是用subrequest来访问一个upstream的后端,并给它一个ngx_http_post_subrequest_t的回调handler,这样有点类似于一个异步的函数调用。对于从upstream返回的数据,subrequest允许根据创建时指定的flag,来决定由用户自己处理(回调handler中)还是由upstream模块直接发送到out put filter。简单的说一下subrequest的行为,nginx使用subrequest访问某个location,产生相应的数据,并插入到nginx输出链的相应位置(创建subrequest时的位置),下面我用agentzh(章亦春,之前是公司北京同事,最近离职了,据说回家专心搞开源)的echo模块(https://github.com/agentzh/echo-nginx-module)来举例说明一下:
location /main {
echo nginx;
echo_location /sub;
echo world;
}
location /sub {
echo hello;
}
访问/main,将得到如下响应:
nginx
hello
world
上面的echo_location指令是发起一个subrequest来访问/sub,echo指定类似shell里面里面的echo,用来输出其后的字符串,顺便说一下echo模块还有其他很多的指令,这个模块在测试的时候非常有用。
在进行源码解析之前,先来想想如果是我们自己要实现subrequest的上述行为,该如何来做?subrequest还可能有自己的subrequest,而且每个subrequest输出数据都不一定是按照其创建的顺序来的,所以这里简单的采用链表来做是不好实现的,于是我们进一步联想到可以采用树的结构来做,主请求即为根节点,每个节点可以有自己的子节点,遍历某节点表示处理某请求,自然的可以想到这里可能是用后根(序)遍历的方法,没错,实际上Igor采用树和链表结合的方式实现了subrequest的功能,但是由于节点(请求)产生数据的顺序不是固定按节点创建顺序(左->右),而且可能分多次产生数据,不能简单的用后根(序)遍历。Igor使用了2个链表的结构来实现,第一个是每个请求都有的postponed链表,一般情况下每个链表节点保存了该请求的一个子请求,该链表节点定义如下:
struct ngx_http_postponed_request_s {
ngx_http_request_t *request;
ngx_chain_t *out;
ngx_http_postponed_request_t *next;
};
可以看到它有一个request字段,可以用来保存子请求,另外还有一个ngx_chain_t类型的out字段,实际上一个请求的postponed链表里面除了保存子请求的节点,还有保存该请求自己产生的数据的节点,数据保存在out字段;第二个是posted_requests链表,它挂载了当前需要遍历的请求(节点), 该链表保存在主请求(根节点)的posted_requests字段,链表节点定义如下:
struct ngx_http_posted_request_s {
ngx_http_request_t *request;
ngx_http_posted_request_t *next;
};
在ngx_http_run_posted_requests函数中会顺序的遍历主请求的posted_requests链表:
void
ngx_http_run_posted_requests(ngx_connection_t *c)
{
...
for ( ;; ) {
/* 连接已经断开,直接返回 */
if (c->destroyed) {
return;
}
r = c->data;
/* 从posted_requests链表的队头开始遍历 */
pr =