Nginx:subrequest的使用方式

转自:https://www.cnblogs.com/runnyu/p/4894329.html

subrequest是由HTTP框架提供的一种分解复杂请求的设计模式。

它可以把原始请求分解为许多子请求,使得诸多请求协同完成一个用户请求,并且每个请求只关注一个功能。

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

1.在nginx.conf文件中配置好子请求的处理方式

2.启动subrequest请求

3.实现子请求执行结束时的回调方法

4.实现父请求被激活时的回调方法

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

配置子请求的处理方式

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

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

location /list {
    proxy_pass http://hq.sinajs.com;
    proxy_set_header Accept-Encoding "";
}

我们还需要配置我们的mytest模块

location /test {
    mytest;
}

启动subrequest请求

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

首先调用ngx_http_subrequest方法建立subrequest子请求,在ngx_http_mytest_handler返回后,HTTP框架会自动执行子请求

先看以下ngx_http_subrequest的定义:

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);

1.ngx_http_request *r 是指当前请求,也就是父请求。

2.ngx_str_t *u 是子请求的URI。

3.ngx_str_t *args  是子请求的URI参数,如果没有参数,可以传送NULL指针。

4.ngx_http_request **psr  产生的子请求将通过这个参数传出去。

5.ngx_http_post_subrequest_t *ps  用来设置子请求处理完毕时的回调方法,下一节有其说明。

6.ngx_uint_t flags 一般设置为0。

下图是subrequest的启动过程序列图

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

Nginx在子请求正常或者异常结束时,都会调用ngx_http_post_subrequest_pt回调方法,它的定义如下

typedef ngx_int_t (*ngx_http_post_subrequest_pt) (ngx_http_request_t *r,void *data,ngx_int_t rc);

在上一节中提到的ngx_http_post_subrequest_t结构如下

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

其中ngx_http_post_subrequest_pt回调方法执行时的data参数就是该结构体中的data成员指针。

该回调方法ngx_http_request_t类型的参数r指的的子请求。

在ngx_http_post_subrequest_pt回调方法内必须设置父请求激活后的处理方法,例如:

r->parent->write_event_handler=mytest_post_handler;

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

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

mytest_post_handler是父请求重新激活后的回调方法,它对应于ngx_http_event_handler_pt指针

typedef void (*ngx_http_event_handler_pt) (ngx_http_request_t *r);
struct ngx_http_request_s {
    ...
    ngx_http_event_handler_pt write_event_handler;
    ...
}

mytest模块完整代码

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

//hq.sinajs.cn/list=s_sh000001

//请求上下文
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
};


//子请求结束时的回调方法
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缓冲区中
        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;
        }
    }
    //设置父请求的回调方法
    pr->write_event_handler=mytest_post_handler;
    return NGX_OK;
}

//父请求的回调方法
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;
    //在内存池上分配内存以保存将要发送的包体
    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;

    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);
}

//启动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));
        if(myctx==NULL)
        {
            return NGX_ERROR;
        }
        ngx_http_set_ctx(r,myctx,ngx_http_mytest_module);
    }
    //ngx_http_post_subrequest_t结构体会决定子请求的回调方法
    ngx_http_post_subrequest_t *psr=ngx_palloc(r->pool,sizeof(ngx_http_post_subrequest_t));
    if(psr==NULL){
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    //设置子请求回调方法为mytest_subrequest_post_handler
    psr->handler=mytest_subrequest_post_handler;
    //将data设为myctx上下文,这样回调mytest_subrequest_post_handler时传入的data参数就是myctx
    psr->data=myctx;
    //子请求的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;
    //调用ngx_http_subrequest创建子请求
    ngx_int_t rc=ngx_http_subrequest(r,&sub_location,NULL,&sr,psr,NGX_HTTP_SUBREQUEST_IN_MEMORY);
    if(rc!=NGX_OK){
        return NGX_ERROR;
    }
    return NGX_DONE;

}

配置好该模块后,利用telnet模拟HTTP请求得到下图

对比与直接访问hq.sinajs.cn/list=s_sh000001

该模块将客户的请求转换成子请求发送个hq.sinajs.cn,然后将其响应的信息修改之后发送给客户。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值