Subrequest实例分析

目录

subrequest 原理和设计
源码分析
实验

正文

系列文章:
upstream demo_fdsafwagdagadg6576的专栏-CSDN博客
Nginx upstream模块与负载均衡模块分析_fdsafwagdagadg6576的专栏-CSDN博客

本文是对Nginx:subrequest的使用方式 - Runnyu - 博客园 实例的详细分析

一 subrequest 原理和设计

1 subrequest介绍         

        subrequest是子请求。它并不是http标准里面的概念,它是在当前请求中发起的一个新的请求,它拥有自己的ngx_http_request_t结构,uri和args。它可以把原始请求分解为许多子请求,使得诸多请求协同完成一个用户请求,并且每个请求只关注一个功能.        
      使用subrequest来访问一个upstream的后端,并给它一个ngx_http_post_subrequest_t的回调handler,这样有点类似于一个异步的函数调用.对于从upstream返回的数据,subrequest允许根据创建时指定的flag,决定由用户自己处理(回调handler中)还是由upstream模块直接发送到output filter.
      简单的说一下subrequest的行为,nginx使用subrequest访问某个location,产生相应的数据,并插入到nginx输出链的相应位置(创建subrequest时的位置).

整体结构
subrequest分成两部分:主请求创建子请求,子请求返回父请求

  

notes: 主请求模块,子请求模块可以是自定义模块,upstream,proxy或者第三方

主请求和子请求的异同
1) 主请求结构体 ngx_http_request,子请求结构体ngx_http_sub_request
2) 都是根据配置文件(eg:/list)调用对应的模块处理.
3) 都是放入queue由http框架调用

2 使用subrequest的方式只需完成以下4个步骤即可:

  • 1.在nginx.conf文件中配置好子请求的处理方式
  • 2.启动subrequest请求
  • 3.实现子请求执行结束时的回调方法
  • 4.实现父请求被激活时的回调方法

notes: 对于程序员来说,仅仅需要实现1个配置和3步的业务逻辑函数即可,框架实现了调用。不用程序员自己实现内部通信.

下面将以mytest模块为例来演示这4个步骤

1)  配置子请求的处理方式

子请求的处理过程与普通请求完全相同。
子请求与普通请求的不同在于:子请求是由父请求生成的,而不是接收客户端发来的网络包再有HTTP框架解析出的。

  • 配置子请求处理模块:

使用ngx_http_proxy_module反向代理模块来处理子请求(假设生成的子请求是以URI为/list开头的请求)。

location /list {
    proxy_pass http://hq.sinajs.com;
    proxy_set_header Accept-Encoding "";
}
  • 配置主请求处理模块
location /test {
    mytest;
}

2) 创建subrequest请求

在ngx_http_mytest_handler处理方法中,可以启动subrequest子请求。

  • 下图是subrequest的启动过程序列图  (同时也是subrequest的模块图结构图)

(1)NGX主循环会定期地调用事件模块,检查是否有网络事件发生;
(2)事件模块发现这个请求的回调方法属于HTTP框架,交由HTTP框架来处理;
(3)根据解析完的URI来决定使用哪个location下的模块来处理这个请求;
(4)调用mytest模块的ngx_http_mytest_handler方法处理这个请求;
(5)设置subrequest子请求的URI及回调方法(ngx_http_mytest_handler);//5-9步请见subrequest使用方式一节介绍
(6)调用ngx_http_subrequest方法创建子请求;
(7)创建的子请求会添加到原始请求的posted_requests链表中;
(8)ngx_http_subrequest方法执行完毕,子请求创建成功;
(9)ngx_http_mytest_handler方法执行完毕,返回NGX_DONE,这样父请求不会被销毁,等待以后的再次激活;
(10)HTTP框架执行完当前请求(父请求)后,检查posted_requests链表中是否还有子请求,如果存在子请求,则调用子请求的write_event_handler方法;
notes:本例中子请求只有handler,没有write_event_handler
(11)根据子请求的URI(本例是/list),检查nginx.conf所有的location配置,确定应有哪个模块执行子请求,比如可以用反向代理模块执行(本例是proxy_pass http://hq.sinajs.com;);
(12)调用模块入口方法来处理子请求,例如反向代理模块入口方法ngx_http_proxy_handler;
notes:本例没有贴出来,在nginx源码有
(13)由于反向代理模块使用了upstream机制,所以它也要通过很多次的异步调用才能完整的处理完子请求,这是它的入口方法返回的是NGX_DONE;
(14)再次检查是否还有子请求,这是已经发现没有子请求了,当然子请求可以继续创建新的子请求,只是这里的反向代理模块不会这样做;
(15)第二步中网络事件处理完毕后,将控制权交给事件模块;
(16)本轮网络事件处理完毕后,交还控制权给NGINX主循环。

3) 实现子请求处理完毕时的回调方法

Nginx在子请求正常或者异常结束时,都会调用ngx_http_post_subrequest_pt回调方法。
ngx_http_post_subrequest_pt回调方法内必须设置父请求激活后的处理方法
例如:r->parent->write_event_handler=mytest_post_handler;
即子请求结束,调用父请求回调函数,执行父请求

  • 下图是子请求激活父请求过程的序列图

(1)nginx主循环定期地调用事件模块,检查是否有网络事件发生;
(2)如果事件模块监测到连接关闭事件,而这个请求的处理方法属于upstream模块,则交由upstream模块来处理请求;
(3) upstream模块开始调用ngx_http_upstream_finalize_request方法来结束upstream机制下的请求(4)调用HTTP框架提供的ngx_http_finalize_request方法来结束子请求;
(5)ngx_http_finalize_request方法会检查当前的请求是否是子请求.如果是,则会回调post_suberquest成员中的handler方法,也就是会调用mytest_subrequest_post_handler方法;
(6)在实现的子请求回调方法中,解析子请求返回的响应包
注意,这时需要通过write_event_handler设置父请求被激活后的回调方法(因为此时父请求的回调方法已经被HTTP框架设置为,什么事都不做的ngx_http_request_empty_handler方法);
(7)子请求的回调方法执行完毕后,交由HTTP框架的ngx_http_finalize_request方法继续向下执行(8)ngx_http_finalize_request方法执行完毕;
(9)HTTP框架如果发现当前请求后还有父请求需要执行,则调用父请求的write_event_handler回调方法;
(10)这里可以根据第6步中解析子请求响应后的结果来构造响应包
(11)调用无阻塞的ngx_http_send_header,ngx_http_output_filter发送方法,向客户端发送响应包(12)无阻塞发送方法会立刻返回,即使目前未发送完,nginx之后也会异步地发送完所有的响应包,然后再结束请求;
(13)父请求的回调方法执行完毕;
(14)当第2步中的上游服务器连接关闭事件处理完毕后,交还控制权给事件模块;
(15)本轮网络事件处理完毕后,交还控制权给nginx主循环。

4) 处理父请求被重新激活后的回调方法

mytest_post_handler是父请求重新激活后的回调方法。将respond发回给client


二 源码分析

Get //hq.sinajs.cn/list=s_sh000001.  subrequest : list=s_sh000001

1 mytest 模块创建

三段式+content pharase handler

//请求上下文
typedef struct {
    ngx_str_t stock[6];
} ngx_http_mytest_ctx_t;

static char *
ngx_http_mytest(ngx_conf_t *cf,ngx_command_t *cmd,void *conf);
static ngx_int_t
ngx_http_mytest_handler(ngx_http_request_t *r);
static void
mytest_post_handler(ngx_http_request_t *r);

static ngx_command_t ngx_http_mytest_commands[]={
    {
        //配置项名称
        ngx_string("mytest"),
        //配置项类型(可出现的位置,参数的个数)   NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS,
        //出现了name中指定的配置项后,将会调用该方法处理配置项的参数
        ngx_http_mytest,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL
    },
    ngx_null_command
};
static char *
ngx_http_mytest(ngx_conf_t *cf,ngx_command_t *cmd,void *conf)
{
    //找到mytest配置项所属的配置块
    ngx_http_core_loc_conf_t *clcf;
    clcf=ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module);
    /*HTTP框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时
        如果请求的主机域名、URI与mytest配置项所在的配置块相匹配,
        就将调用我们事先的ngx_http_mytest_handler方法处理这个请求*/
    clcf->handler=ngx_http_mytest_handler;
    return NGX_CONF_OK;
}
static ngx_http_module_t ngx_http_mytest_module_ctx={
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
};
ngx_module_t ngx_http_mytest_module={
    NGX_MODULE_V1,
    //指向ngx_http_module_t结构体
    &ngx_http_mytest_module_ctx,
    //用来处理nginx.conf中的配置项
    ngx_http_mytest_commands,
    //表示该模块的类型
    NGX_HTTP_MODULE,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NGX_MODULE_V1_PADDING
};

 2 启动子请求subrequest 

ngx_http_mytest_handler在content phrase 阶段调用,创建子请求subrequest。

static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
    //创建HTTP上下文
    ngx_http_mytest_ctx_t *myctx=ngx_http_get_module_ctx(r,ngx_http_mytest_module);
    if(myctx==NULL)
    {
        myctx=ngx_palloc(r->pool,sizeof(ngx_http_mytest_ctx_t));
        ngx_http_set_ctx(r,myctx,ngx_http_mytest_module);
    }
    //1 ngx_http_post_subrequest_t结构体会决定子请求的回调方法
    ngx_http_post_subrequest_t *psr=ngx_palloc(r->pool,sizeof(ngx_http_post_subrequest_t));
    //2 设置子请求回调方法为mytest_subrequest_post_handler//第三方将respond返回到这个callback
    psr->handler=mytest_subrequest_post_handler;
  //3 将data设为myctx上下文,这样回调mytest_subrequest_post_handler时传入的data参数就是myctx
    psr->data=myctx;
    //4 子请求的URI前缀是/list
    ngx_str_t sub_prefix=ngx_string("/list=");
    ngx_str_t sub_location;
    sub_location.len=sub_prefix.len+r->args.len;
    sub_location.data=ngx_palloc(r->pool,sub_location.len);
    ngx_snprintf(sub_location.data,sub_location.len,"%V%V",&sub_prefix,&r->args);
    //sr就是子请求
    ngx_http_request_t *sr;
    //5 调用ngx_http_subrequest创建子请求
    ngx_int_t //参数说明:r是父请求,sub_location是config locate调用的模块即/list(调用第三方模块),sr是子请求,psr是子请求回调方法
    rc=ngx_http_subrequest(r,&sub_location,NULL,&sr,psr,NGX_HTTP_SUBREQUEST_IN_MEMORY);
    return NGX_DONE;
}

步骤说明:request--respond

  • I) 设置子请求回调方法为mytest_subrequest_post_handler
    第三方respond返回到这个callback. 
    psr->handler=mytest_subrequest_post_handler;
  • II) 将data设为myctx上下文,这样回调mytest_subrequest_post_handler时传入的data参数就是myctx
    psr->data=myctx;
  • III) 调用ngx_http_subrequest创建子请求request(子请求入口)
    ngx_http_request_t *sr;//sr就是子请求  rc=ngx_http_subrequest(r,&sub_location,NULL,&sr,psr,NGX_HTTP_SUBREQUEST_IN_MEMORY);

子请求回调函数(第三方将respond返回到这里)

Subrequest框架调用.此处子请求已经从upstream或者后台第三方获取了respond的返回值。然后调用主请求的mytest_post_handler发回.

static ngx_int_t mytest_subrequest_post_handler(ngx_http_request_t *r,
    void *data,ngx_int_t rc)
{
    //获取父请求
    ngx_http_request_t *pr=r->parent;
    //获取上下文
    ngx_http_mytest_ctx_t *myctx=ngx_http_get_module_ctx(pr,ngx_http_mytest_module);

    pr->headers_out.status=r->headers_out.status;
    //查看返回码,如果为NGX_HTTP_OK,则意味访问成功,接着开始解析HTTP包体
    if(r->headers_out.status==NGX_HTTP_OK)
    {
        int flag=0;
        //上游响应会保存在buffer缓冲区中;接收respond的value
        ngx_buf_t *pRecvBuf=&r->upstream->buffer;
        /*解析上游服务器的相应,并将解析出的值赋到上下文结构体myctx->stock数组中
          新浪服务器的返回大致如下:
          var hq_str_s_sh000009=" 上证 380,3356.355,-5.725,-0.17,266505,2519967"*/
        for(;pRecvBuf->pos!=pRecvBuf->last;pRecvBuf->pos++)
        {
            if(*pRecvBuf->pos==','||*pRecvBuf->pos=='\"')
            {
                if(flag>0)
                {
                    myctx->stock[flag-1].len=pRecvBuf->pos-myctx->stock[flag-1].data;
                }
                flag++;
                myctx->stock[flag-1].data=pRecvBuf->pos+1;
            }
            if(flag>6)
                break;
        }
    }
    //设置父请求的回调方法//r->parent->write_event_handler=mytest_post_handler
    pr->write_event_handler=mytest_post_handler;
    return NGX_OK;
}

 4 父请求的回调方法(子请求处理结束调用)

将子请求获取的respond结果拷贝到主请求的http body. 加上主请求的header.
然后使用ngx_http_send_header(r)和ngx_http_output_filter(r,&out)发送给客户端.

static void
mytest_post_handler(ngx_http_request_t *r)
{
    //如果没有返回200,则直接把错误码发送回用户
    if(r->headers_out.status!=NGX_HTTP_OK)
    {
        ngx_http_finalize_request(r,r->headers_out.status);
        return;
    }
    //取出上下文
    ngx_http_mytest_ctx_t *myctx=ngx_http_get_module_ctx(r,ngx_http_mytest_module);
    //定义发给用户的HTTP包体内容
    ngx_str_t output_format=ngx_string("stock[%V],Today current price:%V,volumn:%V");
    //计算待发送包体的长度
    int bodylen=output_format.len+myctx->stock[0].len+
                myctx->stock[1].len+myctx->stock[4].len-6;
    r->headers_out.content_length_n=bodylen;
    //在内存池上分配内存以保存将要发送的包体//将子请求的数据拷贝到临时buffer
    ngx_buf_t *b=ngx_create_temp_buf(r->pool,bodylen);
    ngx_snprintf(b->pos,bodylen,(char *)output_format.data,
                 &myctx->stock[0],&myctx->stock[1],&myctx->stock[4]);
    b->last=b->pos+bodylen;
    b->last_buf=1;
    //获取一个buffer
    ngx_chain_t out;
    out.buf=b;
    out.next=NULL;
    //设置Content-Type
    static ngx_str_t type=ngx_string("text/plain;charset=GBK");
    r->headers_out.content_type=type;
    r->headers_out.status=NGX_HTTP_OK;

    r->connection->buffered|=NGX_HTTP_WRITE_BUFFERED;
    //发送
    ngx_int_t ret=ngx_http_send_header(r);
    ret=ngx_http_output_filter(r,&out);

    ngx_http_finalize_request(r,ret);
}

三 实验测试:

两个scenario
调用mystest模块,使用subrequest

telnet localhost 80;Get /test?s_sh000001 HTTP/1.0
配置好该模块后,利用telnet模拟HTTP请求得到下图

2) 没有使用subrequest

telnet hq.sinajs.cn 90; Get /list=s_sh000001 HTTP/1.0
对比与直接访问hq.sinajs.cn/list=s_sh000001.直接从hq.sinajs.cn获取结果

小结:

1 配置主请求,子请求模块
2 subrequest分成两部分:主请求创建子请求,子请求返回父请求

2.1 主请求创建子请求
1) 配置主请求,子请求处理模块
2) 创建subrequest请求:发送ngx_http_subrequest子请求
3) 实现子请求处理完毕时的回调方法(框架自动调用)
psr->handler=mytest_subrequest_post_handler;
子请求callback获取子请求结果。然后在这个callback处理结果

2.2 子请求返回父请求
1) 创建父请求callback:pr->write_event_handler=mytest_post_handler;
2) 实现父请求callback

仅仅需要实现1个配置和3步的业务逻辑函数即可. 框架实现了调用,不用自己实现内部通信,用户是使用这个模块,不是编写这个模块.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值