参考资料<深入理解Nginx>

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[];
} 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,
,
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=;
//上游响应会保存在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>)
{
myctx->stock[flag-].len=pRecvBuf->pos-myctx->stock[flag-].data;
}
flag++;
myctx->stock[flag-].data=pRecvBuf->pos+;
}
if(flag>)
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[].len+
myctx->stock[].len+myctx->stock[].len-;
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[],&myctx->stock[],&myctx->stock[]);
b->last=b->pos+bodylen;
b->last_buf=; 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,然后将其响应的信息修改之后发送给客户。

Nginx:subrequest的使用方式的更多相关文章

  1. Nginx常见的安装方式

    Nginx常见的安装方式 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Nginx概述 Nginx的安装版本分为开发版.稳定版和过期版, Nginx安装可以使用yum或源码安装 ...

  2. nginx subrequest演示示例程序

    只有简单subrequest应用演示示例. nginx.conf文件: #user nobody; worker_processes 1; #error_log logs/error.log; #er ...

  3. nginx和php-fpm调用方式

    一.背景: 在开发中碰到一个问题,项目以nginx+php-fpm形式访问交互,结果访问项目时报错如下图:   二.分析: 提示很明确嘛,去看error.log(在nginx.conf或者vhost里 ...

  4. [转]Nginx的负载均衡方式

    如果Nginx没有仅仅只能代理一台服务器的话,那它也不可能像今天这么火,Nginx可以配置代理多台服务器,当一台服务器宕机之后,仍能保持系统可用.具体配置过程如下: 1. 在http节点下,添加ups ...

  5. Nginx与Apache工作方式

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://going.blog.51cto.com/7876557/1304204 Ngin ...

  6. nginx与php-fpm通讯方式

    nginx和php-fpm的通信方式有两种,一种是tcp socket的方式,一种是unix socke方式. tcp sockettcp socket的优点是可以跨服务器,当nginx和php-fp ...

  7. Centos7 设置自定义安装nginx的systemctl启动方式

    一.systemctl方式启动设置过程 1.首先创建服务配置文件(名字和路径就是这个) vim /usr/lib/systemd/system/nginx.service 2.添加配置内容 [Unit ...

  8. nginx upstream的分配方式

    1.轮询(默认) 每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除. 2.weight 指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况. 例 ...

  9. CentOs 安装 Nginx + php + mysql (推荐方式)

    本文全部采用yum进行安装, CentOs6.5 mini 版本. 一.更改yum源为网易的源加快速度, 如果是从网易镜像下载的安装包,直接注释这几项也可以 vim /etc/yum.repos.d/ ...

随机推荐

  1. CRichEditCtrl 输入字符串长度限制

    1.我用 CRichEditCtrl 控件,发现它通过代码可以向里面写大于 32KB 的字符,但手工却只能输入小于 32767 个字符,再多则自动舍弃. 2.初始化时调用CRichEditCtrl:: ...

  2. Windows下VS2013创建与使用动态链接库(.dll)

    一.创建动态链接库文件 ** 1.打开VS2013,选择文件,新建工程  2.选择新建W32控制台应用程序,这里将工程名改为makeDLL  3.在应用程序类型中选择DLL,点击完成  4.完成以上步 ...

  3. V++ MFC CEdit输出数组 UNICODE TO ASCII码

    MFC怎么在静态编辑框中输出数组 //字符转ASCII码void CUTF8Dlg::OnBnClickedButtonCharAscii(){ // TODO: 在此添加控件通知处理程序代码 Upd ...

  4. python 错误 error: invalid command 'egg_info'

    Processing /bs4-0.0.1/setuptools-38.4.0/numpy-1.14.0    Complete output from command python setup.py ...

  5. Appium+python自动化9-SDK Manager【转载】

    前言 SDK Manager到有哪些东西是必须安装的呢? 一.SDK Manager 1.双击打开SDK Manager界面

  6. hdu 5108(数论-整数分解)

    Alexandra and Prime Numbers Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (J ...

  7. 计蒜客 28315.Excellent Engineers-线段树(单点更新、区间最值) (Benelux Algorithm Programming Contest 2014 Final ACM-ICPC Asia Training League 暑假第一阶段第二场 E)

    先写这几道题,比赛的时候有事就只签了个到. 题目传送门 E. Excellent Engineers 传送门 这个题的意思就是如果一个人的r1,r2,r3中的某一个比已存在的人中的小,就把这个人添加到 ...

  8. (3)oracle建用户、建表、权限、命名空间

    一.表空间 创建表空间 create tablespace  ts001  datafile ‘d:\test\a.dbf’ size 20m uniform size 128k; 使用表空间 cre ...

  9. Python与数据库[2] -> 关系对象映射/ORM[2] -> 建立声明层表对象的两种方式

    建立声明层表对象的两种方式 在对表对象进行建立的时候,通常有两种方式可以完成,以下是两种方式的建立过程对比 首先导入需要的模块,获取一个声明层 from sqlalchemy.sql.schema i ...

  10. Sql Jions 的简易理解

    Sql Jions 的简易理解 Select  * from TableA A  left jion TableB  B on  A.key = B.key Select  * from TableA ...