什么是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_t ngx_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_t ngx_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_t ngx_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进行处理。
void ngx_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); } }