主请求汇总子请求结果

来源:互联网 发布:如何申请淘宝介入处理 编辑:程序博客网 时间:2024/05/05 00:39

subrequest简介

subrequest,即子请求,它是nginx中的一个概念。它是在当前请求中发起的一个新的请求。比较常见的用法是用利用subrequest来访问一个或者多个upstream的后端,然后以同步或者异步的方式处理返回结果。subrequest最擅长处理的是输出逻辑,在nginx内部已经内建有完整的机制来实现(见ssi模块)。但很多业务逻辑,并不是只有输出逻辑,还有筛选、计算、排序等等逻辑,如果需要以nginx模块的形式来开发的话,利用subrequst仍然是最简单的方法,但复杂程度会比较高。

利用subrequest进行模块开发

subrequest调用法

ngx_int_t                   flags;

ngx_str_t                   loc, args;

ngx_http_request_t         *sr;

ngx_http_post_subrequest_t  *psr;

 

loc.len = sizeof(“/test”) -1;

loc.data = (u_char *)“/test”;

 

args.len =sizeof("arg1=A&arg2=B”) - 1;

args.data = (u_char *)"arg1=A&arg2=B”;

 

psr =ngx_palloc(r->pool,sizeof(ngx_http_post_subrequest_t));

if (psr == NULL){

   return NGX_HTTP_INTERNAL_SERVER_ERROR;

}

 

psr->data =callback_data;

psr->handler= callback_handler;

 

flags =NGX_HTTP_SUBREQUEST_IN_MEMORY|NGX_HTTP_SUBREQUEST_WAITED;

rc = ngx_http_subrequest(r,& loc, &args, &sr,psr, flags);

调用subrequest是利用ngx_http_subrequest函数,这个函数将当前的请求r作为子请求的骨架,再利用传入的loc和args更新子请求的uri和输入参数,将psr作为子请求完成后的回调,最后用flags更新子请求的行为标志,生成的子请求的数据结构ngx_http_request_t会通过sr返回给调用者。

flags有两个标志:

NGX_HTTP_SUBREQUEST_IN_MEMORY:默认子请求的输出是直接返回给客户端的,如果不想返回输出,需要设置NGX_HTTP_SUBREQUEST_IN_MEMORY标志。注意,不是所有Handler模块都支持此标志,比如fastcgi模块不支持,proxy模块支持。

NGX_HTTP_SUBREQUEST_WAITED:子请求完成后会设置自己的r->done标志位,可以通过判断标志位得知子请求是否完成。

 

回调数据ngx_http_post_subrequest_t是子请求处理完成后在ngx_http_finalize_request调用的,如果没有回调函数,调用ngx_http_subrequest时将psr设为NULL。回调函数的声明是ngx_int_t callback_handler(ngx_http_request_t *r,void *data, ngx_int_t rc)

这里的r是子请求,它的父请求是r->parent,它的祖先请求是r->main。data是定义callback_handler的psr中同时定义的data,可以为NULL,设置为NULL时回调函数的data参数就是NULL,rc是子请求处理的返回值,这个对于不同的子请求肯定是不一样的,但一般说来,子请求正常完成,rc一定是NGX_OK。如果子请求失败了,会返回NGX_ERROR等等错误代码(负值)或者子请求所用协议的状态码(比如子请求使用proxy模块作为Handler的话会返回403、501等等HTTP状态)。

subrequest运行时

subrequest是一种复杂机制,所以无法像之前那样给出简单的例子。这里需要简单分析subrequest的运行机制,只是很简单的分析。

 SHAPE  \*MERGEFORMAT

调用ngx_http_subrequest

处理主请求

继续处理主请求,返回

执行子请求

执行子请求完毕,调用回调,设置r->done

继续处理主请求

可以看到,当我们调用ngx_http_subrequest,我们只是告诉nginx,我们有一个子请求需要执行,而这时子请求还没有执行。主请求根据自己的情况决定是否继续执行下去,还是先让子请求执行。子请求执行的时机一定是在主请求交出执行权力以后。子请求执行完毕后,父请求会继续执行。“继续”是宏观意义上的,具体的意思我们在《Nginx模块开发(五)》中详细讨论,大致的说法就是,nginx知道自己应该从哪个处理函数开始执行主请求,但是函数内部的执行状态,nginx不知道,需要函数自己维护。唯一的例外是输出逻辑,nginx非常清楚自己应该怎么执行下去。


 

subrequest与Filter

Filter中使用subrequest可以使用类似于下面的代码段。这个例子是需要处理有多个subrequest的情况(如果只有一个,可以不用subrequest数组):

typedef struct {

   ngx_str_t uri;

   ngx_str_t args;

   ngx_http_request_t *sr;

   ngx_uint_t flag;

}my_module_subrequest_conf_t;

 

static ngx_int_t

my_module_body_filter(ngx_http_request_t *r, ngx_chain_t*in)

{

   int        rc;

   nbsp;;my_ctx_t *ctx;

 

   if (r != r->main) {

       return ngx_http_next_body_filter(r, in);

   }

 

   ctx = ngx_http_get_module_ctx(r, my_module);

   if (ctx == NULL) {

       

   }

 

   do {

       subrequest = ((my_module_subrequest_conf_t *)ctx->subrequests.elts)[ctx->i++];

       rc = ngx_http_subrequest(r, &subrequest.uri,&subrequest.args,&subrequest.sr,

                            NULL,subrequest.flags);

   } while (rc == NGX_OK &&ctx->i <ctx->subrequests.nelts);

 

   return rc;

}

首先,现在的ngx_http_subrequest只会返回NGX_OK或者NGX_ERROR,可能古老的版本情况会多一些,现在已经遇不到了。其次,r != r->main的判断是必须的,因为filter的全局性。再次,只要创建一个subrequest失败,就应该立即返回。最后,filter函数是可能被多次执行的,其中有可能出现in是NULL的情况,这点要注意。

 

上面的例子负责发送所有的子请求,如果子请求的逻辑是直接输出内容,那么不需要添加其他代码,nginx会帮助我们自动完成后面的工作。如果需要等待子请求处理完成并一次性对子请求输出做过滤后再输出,那么情况要复杂一些,需要自己判断子请求是否处理完毕,同时还要自行处理子请求的返回。整个框架类似下面的代码:

 

 

static ngx_int_t

my_module_body_filter(ngx_http_request_t *r, ngx_chain_t*in)

{

   int        rc;

   my_ctx_t  *ctx;

   ngx_uint_t i;

 

   if (r != r->main) {

       return ngx_http_next_body_filter(r, in);

   }

 

   ctx = ngx_http_get_module_ctx(r, my_module);

   if (ctx == NULL) {

       

   }

 

   if (ctx->i <ctx->subrequests.nelts) {

       do{

           subrequest= ((my_module_subrequest_conf_t *)ctx->subrequests.elts)[ctx->i++];

           rc= ngx_http_subrequest(r, &subrequest.uri,&subrequest.args,&subrequest.sr,

                                NULL,subrequest.flags);

       }while (rc == NGX_OK &&ctx->i <ctx->subrequests.nelts);

   } else {

       for (i = 0; i <ctx->subrequests.nelts; i++) {

           subrequest = ((my_module_subrequest_conf_t *)ctx->subrequests.elts)[i];

           if (!subrequest.sr->done) {

               return NGX_AGAIN;

           }

       }

 

       rc =

   }

 

   return rc;

}

这里首先要设置flag,使子请求不输出结果并在结束时设置sr->done属性。其次是判断所有请求是否处理完毕,使用的方法是判断所有的sr->done标记是否为1。最后我们需要自己去子请求的数据结构(sr)中取得子请求的结果,并自己处理这些结果。

利用这种方式时,需要注意NGX_HTTP_SUBREQUEST_IN_MEMORY标记是否被子请求所支持。如果不支持的话,建议仅使用nginx作为网关,将请求转发到后端php\python\java等等,然后在这些后端实现逻辑。

最后还有一种情况,某请求需要发送一个子请求,等待子请求完成,根据这个子请求结果发送下一个子请求。那么在filter中需要记录当前的处理状态,类似于下面这段代码:

 

 

static ngx_int_t

my_module_body_filter(ngx_http_request_t *r, ngx_chain_t*in)

{

   int        rc;

   my_ctx_t  *ctx;

   ngx_uint_t i;

 

   if (r != r->main) {

       return ngx_http_next_body_filter(r, in);

   }

 

   ctx = ngx_http_get_module_ctx(r, my_module);

   if (ctx == NULL) {

       

   }

 

   if (ctx->i <ctx->subrequests.nelts) {

       subrequest = ((my_module_subrequest_conf_t *)ctx->subrequests.elts)[ctx->i];

       if (subrequest.sr == NULL) {

           rc = ngx_http_subrequest(r, &subrequest.uri,&subrequest.args,&subrequest.sr,

                                NULL,subrequest.flags);

       }else {

           if (!subrequest.sr->done) {

               return NGX_AGAIN;

           } else {

               rc =

               i++;

           }

       }

   }

 

   return rc;

}

我们这里使用ctx->i和subrequest->sr联合起来保存当前处理状态。ctx->i保存处理到第几个子请求的状态,subrequest->sr反映当前子请求是未发送还是已发送的状态。

subrequest与Handler

在Handler中使用subrequest需要多一些处理挂接nginx的事件的逻辑,这是与在Filter中使用subrequest最大的不同,因为对于后者,nginx有一块逻辑自动设置事件,形成一个可以运动的循环,是Filter能够一直正确的处理,而在Handler中使用subrequest,我们其实是破坏了nginx本身的循环,所以需要编程人员自己构建一个事件循环来完成自己的工作。

我们在这里只介绍一种最容易理解的循环。画个流程图来展现这个循环:

 SHAPE  \*MERGEFORMAT

nginx使用ngx_http_core_run_phases处理主请求

nginx使用ngx_http_request_handler处理子请求

 

nginx使用ngx_http_core_run_phases继续处理主请求

 

但是nginx默认的循环不是它,实际上,大部分情况下,nginx使用下面循环:

 SHAPE  \*MERGEFORMAT

nginx使用ngx_http_core_run_phases处理主请求

nginx使用ngx_http_request_handler处理子请求

 

nginx使用ngx_http_writer继续处理主请求

 

所以,我们的程序需要如下设计:

1.        Handler应使用状态机的方式来编程,状态记录在module_ctx中;

2.        使用module_ctx传递子请求的结果,也可以使用前面的sr来引用结果;

3.        子请求的结果如需要字符串解析,也需要利用状态机,因为返回是一个chain;

4.        使用subrequest的回调函数来完成循环的更新。

 

一个简单的轮回如下例:

typedef struct {

   ngx_int_t      state;

   ngx_int_t      res;

} my_ctx_t;

 

static ngx_int_t

my_handler(ngx_http_request_t *r)

{

   my_ctx_t         *ctx;

   ngx_http_post_subrequest_t  *psr;

 

   ctx = ngx_http_get_module_ctx(r, my_module);

 

   if (ctx == NULL) {

       ctx = ngx_pcalloc(r->pool,sizeof(my_ctx_t));

       if (ctx == NULL) {

           return NGX_HTTP_INTERNAL_SERVER_ERROR;

       }

 

       ngx_http_set_ctx(r, ctx, my_module);

       ctx->res = -1;

       ctx->state = MY_START;

   }

 

   switch (ctx->state) {

       case MY_START:

           psr = ngx_palloc(r->pool,sizeof(ngx_http_post_subrequest_t));

           if (psr == NULL) {

               return NGX_HTTP_INTERNAL_SERVER_ERROR;

           }

 

           psr->data = ctx;

           psr->handler =my_subrequest_post_handler;

 

           flags = NGX_HTTP_SUBREQUEST_IN_MEMORY;

           rc = ngx_http_subrequest(r, &loc,&args, &sr, psr, flags);

           if (rc != NGX_OK) {

               return NGX_HTTP_INTERNAL_SERVER_ERROR;

           }

 

           r->main->count++;

           return NGX_DONE;

 

       case MY_SECOND_SUBREQUEST:

           

 

           r->main->count++;

           return NGX_DONE;

 

       case MY_LAST_STEP:

 

 

           return NGX_OK;

}

 

   

   return NGX_ERROR;

}

 

static ngx_int_t

my_subrequest_post_handler(ngx_http_request_t *r, void *data, ngx_int_t rc)

{

   char                   *p, next_char, buffer[NGX_HTTP_UCS_BUFFER];

   ngx_int_t               state, len;

   ngx_http_ucs_ctx_t     *ctx;

 

   ctx = data;

   ctx->state = MY_SECOND_SUBREQUEST;

 

   r->parent->write_event_handler =ngx_http_core_run_phases;

 

   if (rc == MY_CONDITION) {

       ctx->res = MY_RESULT;

       ctx->state = MY_LAST_STEP;

       return NGX_OK;

   }

 

if ( ) {

       return NGX_ERROR;

   }

 

   return NGX_OK;

}

大家可以注意代码里面的几点:

1.        子请求传递内容给父请求,可以通过父请求将自己的ctx作为子请求回调函数的data,子请求回调函数修改data的内容。当然,子请求通过ngx_http_get_module_ctx(r->parent,my_module)也可以做到

2.        子请求修改父请求的写事件回调函数:

r->parent->write_event_handler= ngx_http_core_run_phases

3.        Handler没有完成所有逻辑时,一律以下列方式返回:

r->main->count++;

return NGX_DONE;


 
0 0
原创粉丝点击