nginx访问第三方服务之subrequest使用

subrequest是由HTTP框架提供的一种分解复杂请求的设计模式,可以把原始请求分解为子请求,在学习其工作机制之前,我先在网上找了一个简单例子,大概了解了了一蛤子请求大概是个什么东西,之后具体学习了其流程。

1.subrequest简单示例

这个例子要先将addition编入nginx核心:

./configure --with-http_addition_module
make;sudo make install

编写nginx.conf文件


#user  root;
worker_processes  1;


error_log  logs/error.log  debug;

events {
    worker_connections  1024;
}


http {
        include       mime.types;
        default_type  application/octet-stream;

        #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
        #                  '$status $body_bytes_sent "$http_referer" '
        #                  '"$http_user_agent" "$http_x_forwarded_for"';

        #access_log  logs/access.log  main;

        keepalive_timeout  65;

        server {
            listen 1024;
    #   location /test {
    #       mytest;
    #   }


        location /main.htm{
            root html;
            index main.htm;
            add_before_body /hello.htm;
            add_after_body /world.htm;
        }
        location /hello.htm {
            root html/hello;
        }
        location /world.htm {
            root html/world;
        }
    }
}

/usr/local/nginx/html(nginx默认安装目录),下创建相关目录及文件:

cd /usr/local/nginx/html
sudo vi main.htm
#编辑main.htm
mkdir hello;cd hello
sudo vi hello.htm #编辑hello.htm
cd ..
mkdir world;cd world
sudo vi world.htm #编辑world.htm

启动Nginx,在浏览器输入localhost:1024/main.htm
效果如图:

这里写图片描述


2.subrequest的使用方式

接下来学习了《深入理解Nginx》,学习的最大收获应该是陶辉老师是中国股民,呵呵。

使用subrequest的方式要比upstream简单得多,需要完成以下4步:

1.在nginx.conf 文件中配置好子请求的处理方式(本例将使用upstream访问sina股票服务)
2.启动subrequest子请求。
3.实现子请求执行结束时的回调方法。
4.实现父请求被激活时的回调方法。

2.1 配置nginx.conf


#user  nobody;
worker_processes  1;

error_log  logs/error.log  debug;

events {
    worker_connections  1024;
}


http {
        include       mime.types;
        default_type  application/octet-stream;

        #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
        #                  '$status $body_bytes_sent "$http_referer" '
        #                  '"$http_user_agent" "$http_x_forwarded_for"';

        #access_log  logs/access.log  main;

        keepalive_timeout  65;

        server {
            listen 8080;

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

        location /query {
            mytest;
        }
    }
}

2.2 启动subrequest子请求

当解析到mytest配置项时,在相关回调函数中,可以设置handler并在该处理方法中启动subrequest子请求。
这里这要用的一个函数是:

ngx_int_t
ngx_http_subrequest(ngx_http_request_t *r,  // 父请求
ngx_str_t *uri,                             //请求URI
ngx_str_t *args,                            //子请求的URI参数
ngx_http_request_t **psr,                   //返回创建好的子请求(输出参数)
ngx_http_post_subrequest_t *ps,             //子请求结束时的回调函数
ngx_uint_t flags                            //subrequest_in_memory标志位
);

2.3 子请求结束时的回调函数

nginx在子请求正常或者异常结束时,都会调用ngx_http_post_subrequest_pt回调,如下所示,代码见http/ngx_http_request.h

typedef ngx_int_t (*ngx_http_post_subrequest_pt) (ngx_http_request_t *r,
void*data,          //回调函数的参数
ngx_int_t rc        //HTTP响应码
);

将该回调函数封装到ngx_http_post_subrequest_t结构体中

typedef struct{
    ngx_http_post_subrequest_pt handler;
    void *data;//data即回调函数的参数data
}ngx_http_post_subrequest_t;

有了结构体,为结构体分配内存,设置相关回调,例如:

//...
ngx_http_post_subrequest_t *psr=ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
/*设置子请求处理完毕时回调方法为mytest_subrequest_post_handler*/
psr->handler = mytest_subrequest_post_handler;
/*回调函数的data参数*/
psr->data = myctx;
//...

2.4 设置父请求重新激活后的回调方法

当启动子请求后,父请求并没有销毁,激活父请求的操作需要在子请求结束时的回调函数中执行。
例如在mytest_subrequest_post_handler回调函数中设置:

ngx_http_request_t *pr =r->parent;//当前r是子请求
pr->write_event_handler=mytest_post_handler; 

3.完整代码及测试

3.1代码

注:源码来自《深入理解Nginx》和我自己的注释

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


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

/*在ngx_http_mytest中设置的回调,启动subrequest子请求*/
static ngx_int_t
ngx_http_mytest_handler(ngx_http_request_t *r);


/*父请求重新被激活后的回调方法*/
static void
mytest_post_handler(ngx_http_request_t * r);

/*配置项处理函数*/
static char*
ngx_http_mytest(ngx_conf_t *cf ,ngx_command_t *cmd ,void *conf);

/*子请求结束时的回调函数*/
static ngx_int_t
mytest_subrequest_post_handler(ngx_http_request_t *r, void *data, ngx_int_t rc);

/*模块 commands*/
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,
        ngx_http_mytest,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL
    },

    ngx_null_command
};

/*http模块上下文*/
static ngx_http_module_t ngx_http_mytest_module_ctx=
{
    NULL,   /* preconfiguration */
    NULL,   /* postconfiguration */
    NULL,   /* create main configuration */
    NULL,   /* init main configuration */
    NULL,   /* create server configuration */
    NULL,   /* merge server configuration */
    NULL,   /* create location configuration */
    NULL    /* merge location configuration */
};

/*nginx 模块*/
ngx_module_t  ngx_http_mytest_module =
{
    NGX_MODULE_V1,
    &ngx_http_mytest_module_ctx,           /* module context */
    ngx_http_mytest_commands,              /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};




/*配置项处理函数*/
static char*
ngx_http_mytest(ngx_conf_t *cf,ngx_command_t*cmd,void *conf)
{

    ngx_http_core_loc_conf_t  *clcf;

    /*找到mytest配置项所属的配置块*/
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

    /*当请求URI匹配时将调用handler来处理请求*/
    clcf->handler = ngx_http_mytest_handler;

    return NGX_CONF_OK;
}



/*子请求结束时回调该方法*/
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;
    /*访问服务器成功,开始解析包体*/
    if(NGX_HTTP_OK == r->headers_out.status)
    {
        int flag = 0;

        ngx_buf_t* pRecvBuf = &r->upstream->buffer;
        /*内容解析到stock数组中*/
        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);


    /*发送给客户端的包体内容*/

    ngx_str_t output_format = ngx_string("stock[%V],Today current price: %V, volumn: %V");

    /*计算待发送包体的长度*/
    /*-6减去占位符*/
    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,汉子编码是GBK*/
    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;

    /*发送http头部;包括响应行*/
    ngx_int_t ret = ngx_http_send_header(r);
    /*发送http包体*/
    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)
{

    /*创建上下文*/
    ngx_http_mytest_ctx_t* myctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);

    if(NULL == myctx)
    {
        myctx = ngx_palloc(r->pool,sizeof(ngx_http_mytest_ctx_t));
        if (myctx == NULL)
        {
            return NGX_ERROR;
        }

        /*将上下文设置到原始请求r中*/
        ngx_http_set_ctx(r, myctx, ngx_http_mytest_module);
    }

    /*子请求的回调方法将在此结构体中设置*/
    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参数*/
    psr->data = myctx;


    /*构造子请求*/
    /*sina服务器要求*/
    ngx_str_t sub_prefix = ngx_string("/list=");

    /*URL构造=前缀+参数*/
    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;
    /*创建子请求*/
    /*参数分别是:*/
    //1.父请求 2.请求URI 3.子请求的URI参数 4.返回创建好的子请求
    //5.子请求结束的回调
    //6.subrequest_in_memory 标识
    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;
    }

    /*必须返回NGX_DONE*/
    /*父请求不会被销毁,而是等待再次被激活*/
    return NGX_DONE;
}

3.2测试

将mytest模块编入nginx

./configure --add-module=/home/zhangxiao/zxtest/nginxlearn/chapter5/subrequest/
make;sudo make install

重启nginx,在浏览器输入localhost:8080/query?s_sh000001今天的上证指数。

这里写图片描述

顺便看了看百度的上证指数图,呵呵。
这里写图片描述


4.参考

1.深入理解Nginx
2.https://www.kancloud.cn/kancloud/master-nginx-develop/51853

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值