六狼论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

新浪微博账号登陆

只需一步,快速开始

搜索
查看: 51|回复: 0

nginx中sub_request的处理

[复制链接]

升级  98%

57

主题

57

主题

57

主题

秀才

Rank: 2

积分
197
 楼主| 发表于 2013-2-4 23:48:44 | 显示全部楼层 |阅读模式
首先来看subrequest的处理。

什么是subrequest,顾名思义,那就是子请求,也就是在当前的一个请求中nginx再生成一个请求。比如在nginx的HttpAddition这个filter,就有用到subrequest。

这里要注意,一个subrequest是当父reuest执行完毕后才会被执行,并且它会将所有的需要进行的handler phase重新执行一遍(这个我们后面的代码会看到).

这里还涉及到一个很关键的filter,那就是postpone filter,这个filter就用来缓存住父request,这里的缓存就是将需要发送的数据保存到一个链表中。这个是因为会先执行subrequest,然后才会执行request,因此如果有subrequest的话,这个filter就会跳过后面的发送filter,直接返回ok。

sub request是通过post pone filter以及finalize_request,还有下面的三个域来配合实现的,接下来我们会一个个的分析。

因此这里有这样三个概念,一个是postpone_request,一个是post_request,一个是post_subrequest.其实这三个也就是request的三个域了:

//这个表示主的request,也就是当前的request链中最上面的那个request,通过这个域我们就能判断当前的request是不是subrequest。ngx_http_request_t               *main;//这个表示当前的request的父request。    ngx_http_request_t               *parent;//最关键就是下面三个域。    ngx_http_postponed_request_t     *postponed;    ngx_http_post_subrequest_t       *post_subrequest;    ngx_http_posted_request_t        *posted_requests;

ok,我们一个个来看,先来看postponed,这个域用来缓存父request的数据(也就是将要发送数据的request),而缓存这个动作是在postpone filter中来做的,我们后面回来分析这个filter。下面就是它的结构:

struct ngx_http_postponed_request_s {    ngx_http_request_t               *request;    ngx_chain_t                      *out;    ngx_http_postponed_request_t     *next;};

可以看到它就是一个很简单的链表,三个域的意思分别为:
request 保存了subrequest
out保存了所需要发送的chain。
next保存了下一个postpone_request.

然后是post_subrequest,这个域保存了子请求的post request,它也就是保存了需要被发送的request. 来看它的结构:

typedef struct {    ngx_http_post_subrequest_pt       handler;    void                             *data;} ngx_http_post_subrequest_t;

可以看到它的结构更加简单,一个handler,保存了到时需要执行的回掉函数,一个data,保存了传递的数据。

最后是posted_requests,这个保存了所有的需要处理的request链表,也就是说它即包含子请求也包含父请求。来看它的结构:

struct ngx_http_posted_request_s {    ngx_http_request_t               *request;    ngx_http_posted_request_t        *next;};

request保存了需要处理的request,next保存了下一个需要处理的request。

然后我们来详细分析sub request的处理流程以及代码,这里代码的分析顺序是按照sub request的流程来的。

首先来看sub request的设置函数ngx_http_subrequest。

这个函数的主要功能就是新建一个request,然后设置对应的属性,其中大部分属性都是和父request相同的,还有一些特殊的sub request独有的属性我们会在下面的代码中分析到(主要是我上面介绍的4个域)。

这个函数的参数比较多,有6个参数,来看它的原型:
ngx_int_tngx_http_subrequest(ngx_http_request_t *r,    ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr,    ngx_http_post_subrequest_t *ps, ngx_uint_t flags)
r表示需要生成子请求的request,uri表示子请求的uri,args表示子请求的参数,psr表示最终生成的子请求,ps表示子请求的post_subrequest,flags主要控制sub request的内容是否要放到内存中。

代码比较长,我们分开来看,下面这段是相关的初始化:

ngx_connection_t              *c;    ngx_http_request_t            *sr;    ngx_http_core_srv_conf_t      *cscf;    ngx_http_postponed_request_t  *pr, *p;//subrequest表示了当前还可以处理的最大个数的sub request,这个值的默认值是50.表示nginx中能够处理的最多的嵌套子请求的个数是50.    r->main->subrequests--;//如果为0,则表示已达到最大的限制,因此返回error。    if (r->main->subrequests == 0) {        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,                      "subrequests cycle while processing \"%V\"", uri);        r->main->subrequests = 1;        return NGX_ERROR;    }//新建一个sub request。    sr = ngx_pcalloc(r->pool, sizeof(ngx_http_request_t));    if (sr == NULL) {        return NGX_ERROR;    }    sr->signature = NGX_HTTP_MODULE;//设置connection。    c = r->connection;    sr->connection = c;//下面的初始化大部分都是和父请求一样的。    sr->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);    if (sr->ctx == NULL) {        return NGX_ERROR;    }    if (ngx_list_init(&sr->headers_out.headers, r->pool, 20,                      sizeof(ngx_table_elt_t))        != NGX_OK)    {        return NGX_ERROR;    }......................................................    sr->request_body = r->request_body;//可以看到子请求只会是Get方法。    sr->method = NGX_HTTP_GET;    sr->http_version = r->http_version;    sr->request_line = r->request_line;

接下来这段也是初始化,只不过主要是初始化一些sub request特有的属性。这里最关键的就是两个事件处理函数的赋值,read_event_handler和write_event_handler 。其中读事件的handler被赋值为一个空的函数,也就是在sub request中,不会处理读事件。而写事件的handler被赋值为ngx_http_handler,这个函数我们知道,它就是整个nginx的handler处理的入口,因此也就是说sub request最终会把所有的phase再重新走一遍。

这里还要注意,那就是父请求可能会有多个儿子请求。
//子请求的uri    sr->uri = *uri;    if (args) {//参数设置        sr->args = *args;    }    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,                   "http subrequest \"%V?%V\"", uri, &sr->args);//子请求的内容是否需要放到内存中。    sr->subrequest_in_memory = (flags & NGX_HTTP_SUBREQUEST_IN_MEMORY) != 0;//这个貌似是ssi用到的。    sr->waited = (flags & NGX_HTTP_SUBREQUEST_WAITED) != 0;..............................................................................    ngx_http_set_exten(sr);//设置main,也就是最上层的那个request。    sr->main = r->main;//设置父request    sr->parent = r;//可以看到ost_subrequest 被设置为我们传递进来的值。    sr->post_subrequest = ps;//读写事件的处理函数的赋值    sr->read_event_handler = ngx_http_request_empty_handler;    sr->write_event_handler = ngx_http_handler;//设置连接的request为子请求。这里的意思是如果父请求设置第二个子请求的话,这里就不需要设置连接的request了。    if (c->data == r && r->postponed == NULL) {        c->data = sr;    }    sr->variables = r->variables;    sr->log_handler = r->log_handler;//开始赋值postponed request.    pr = ngx_palloc(r->pool, sizeof(ngx_http_postponed_request_t));    if (pr == NULL) {        return NGX_ERROR;    }//它的request设置为子请求,也就是每个子请求都会用一个postponed request包装起来。    pr->request = sr;    pr->out = NULL;    pr->next = NULL;//如果是第一次给父请求设置孩子,那么将pr放到postponed链表的结尾。    if (r->postponed) {        for (p = r->postponed; p->next; p = p->next) { /* void */ }//找到尾部,然后插入。        p->next = pr;    } else {//否则直接设置        r->postponed = pr;    }//设置内部标记    sr->internal = 1;    sr->discard_body = r->discard_body;    sr->expect_tested = 1;    sr->main_filter_need_in_memory = r->main_filter_need_in_memory;    sr->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;//subrequests加1.    r->main->subrequests++;//保存生成的sub request,以供外部使用。    *psr = sr;//设置post request。    return ngx_http_post_request(sr);}

上面的代码有个有疑问的地方,那就是subrequests,我查了下代码只有这个函数里面有对它进行操作,可是这里前面--,后面++,那不是基本没有可能这个值是0。

前面的代码我们可以看到最后会调用ngx_http_post_reques来处理,这个函数是用来讲subrequest放到post request中的。

而post request的调用我会在后面分析到。

ngx_int_tngx_http_post_request(ngx_http_request_t *r){    ngx_http_posted_request_t  *pr, **p;//新建一个post request。    pr = ngx_palloc(r->pool, sizeof(ngx_http_posted_request_t));    if (pr == NULL) {        return NGX_ERROR;    }//设置request为sub request.    pr->request = r;    pr->next = NULL;//找到post request的尾部。    for (p = &r->main->posted_requests; *p; p = &(*p)->next) { /* void */ }//然后赋值。    *p = pr;    return NGX_OK;}

然后来看postpone 这个filter,这个filter就是用来缓存父request的chain, 并且控制sub request的发送。

代码分段来看,先来看第一部分,这部分主要是处理父请求进来的情况,也就是缓存父请求的chain。

ngx_connection_t              *c;    ngx_http_postponed_request_t  *pr;//取得当前的链接    c = r->connection;//如果r不等于c->data,前面的分析知道c->data保存的是最新的一个sub request(同级的话,是第一个),因此不等于则说明是需要保存数据的父request。    if (r != c->data) {        if (in) {//保存数据(下面会分析这段代码)            ngx_http_postpone_filter_add(r, in);//这里注意不发送任何数据,直接返回OK。而最终会在finalize_request中处理。            return NGX_OK;        }        return NGX_OK;    }//如果r->postponed为空,则说明是最后一个sub request,也就是最新的那个,因此需要将它先发送出去。    if (r->postponed == NULL) {//如果in存在,则发送出去        if (in || c->buffered) {            return ngx_http_next_filter(r->main, in);        }        return NGX_OK;    }
然后来看ngx_http_postpone_filter_add这个方法,这个方法主要是拷贝当前需要发送的chain到postponed的out域中。

这里要注意一个的就是由于filter有可能会进入多次,因此如果相同的request的in chain会拷贝到相同的posrponed request中。

还有这里要注意就是这里添加的postponed request的request域是NULL,也就是说明这个postponed request就是自己,也就是r==r->postponed->request.

static ngx_int_tngx_http_postpone_filter_add(ngx_http_request_t *r, ngx_chain_t *in){    ngx_http_postponed_request_t  *pr, **ppr;//如果postponed存在,则进入相关处理    if (r->postponed) {//找到postponed的尾部        for (pr = r->postponed; pr->next; pr = pr->next) { /* void */ }//如果为空,则直接添加到当前的chain        if (pr->request == NULL) {            goto found;        }        ppr = &pr->next;    } else {        ppr = &r->postponed;    }    pr = ngx_palloc(r->pool, sizeof(ngx_http_postponed_request_t));    if (pr == NULL) {        return NGX_ERROR;    }    *ppr = pr;//可以看到request是空。    pr->request = NULL;    pr->out = NULL;    pr->next = NULL;found://最终复制in到pr->out,也就是保存request 需要发送的数据。    if (ngx_chain_add_copy(r->pool, &pr->out, in) == NGX_OK) {        return NGX_OK;    }    return NGX_ERROR;}

然后再回到ngx_http_postpone_filter,剩下的这段代码主要就是用来发送前面保存的父请求的chain.

//到达这里说明需要发送父请求的数据了。if (in) {//如果有chain,则保存数据。        ngx_http_postpone_filter_add(r, in);    }//开始遍历postponed request.    do {        pr = r->postponed;//如果存在request,则说明这个postponed request是sub request,因此需要将它放到post_request中。        if (pr->request) {            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,                           "http postpone filter wake \"%V?%V\"",                           &pr->request->uri, &pr->request->args);            r->postponed = pr->next;            c->data = pr->request;//放到post request中。            return ngx_http_post_request(pr->request);        }        if (pr->out == NULL) {            ngx_log_error(NGX_LOG_ALERT, c->log, 0,                          "http postpone filter NULL output",                          &r->uri, &r->args);        } else {//说明pr->out不为空,此时需要将保存的父request的数据发送。            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,                           "http postpone filter output \"%V?%V\"",                           &r->uri, &r->args);//发送            if (ngx_http_next_filter(r->main, pr->out) == NGX_ERROR) {                return NGX_ERROR;            }        }        r->postponed = pr->next;    } while (r->postponed);

然后我们来看ngx_http_finalize_request中sub request的处理部分,其实ngx_http_finalize_request中,一大部分都是sub request的处理,

这个处理其实主要就是修改一开始介绍的request的三个域。

这里要注意,由于如果有sub request的话,postponed filter会返回NGX_OK。

还有就是所有需要处理的request都必须放入到post request中,儿postponed request中保存的是暂时缓存的不需要发送的request。
   //如果r不等于r->main的话,则说明当前的请求是是sub request,此时进入相关处理。 if (r != r->main) {//如果含有postponed的话,则说明这个request并不是最后一个sub request,因此设置write handler,并且返回。        if (r->buffered || r->postponed) {            if (ngx_http_set_write_handler(r) != NGX_OK) {                ngx_http_close_request(r->main, 0);            }            return;        }//取得request的父request        pr = r->parent;//如果r等于c->data,则说明当前是最后一个sub request,此时需要修改c->data,以便于在postponed filter中发送保存的父request的数据。        if (r == c->data) {...................................................................            r->done = 1;//如果父request的postponed存在并且它的request为当前的r,则开始处理接下来的postponed。            if (pr->postponed && pr->postponed->request == r) {//取next                pr->postponed = pr->postponed->next;            }//修改c->data,这个将会在run_post_request中使用,接下来就会分析这个函数。            c->data = pr;        } else {//否则则设置write handler.            r->write_event_handler = ngx_http_request_finalizer;            if (r->waited) {                r->done = 1;            }        }//最终将pr也就是父request放入到post request中。        if (ngx_http_post_request(pr) != NGX_OK) {            ngx_http_close_request(r->main, 0);            return;        }..............................................        return;    }

上面有一个函数那就是ngx_http_set_write_handler,这个用来设置write handler,这里是这是write handler为ngx_http_writer,而我们要知道sub request的处理是,不停的保存父request的chain(在postponed filter中),而每次保存完毕之后就返回ngx_ok,此时由于这个request已经经历完毕所有的handler phase,因此我们就需要修改它的write handler,以便于需要发送的时候跳过handler 阶段,因此就设置ngx_http_writer,这个函数主要就是调用out_put filter,而不经过handler phase。

最后我们来看post request的调用在那里。

在nginx中,request的执行是在ngx_http_process_request中的,来看这个函数的最后两句:

//处理request,每个sub request在处理之前的write handler 都是这个函数。ngx_http_handler(r);//开始run post request    ngx_http_run_posted_requests(c);

我们来详细看ngx_http_run_posted_requests的实现。这个函数就是遍历post request,然后调用它的write handler对request进行处理。

voidngx_http_run_posted_requests(ngx_connection_t *c){    ngx_http_request_t         *r;    ngx_http_log_ctx_t         *ctx;    ngx_http_posted_request_t  *pr;//开始遍历    for ( ;; ) {//如果连接已经被销毁,则直接返回。        if (c->destroyed) {            return;        }//取出c->data(可以看到在finalize request中会修改到这个域的,也就是这个域会始终保存最新的sub request(也就是将要被发送的request).        r = c->data;        pr = r->main->posted_requests;        if (pr == NULL) {            return;        }//赋值为下一个。        r->main->posted_requests = pr->next;        r = pr->request;        ctx = c->log->data;        ctx->current_request = r;//调用write handler.        r->write_event_handler(r);    }}
您需要登录后才可以回帖 登录 | 立即注册 新浪微博账号登陆

本版积分规则

快速回复 返回顶部 返回列表