Nginx:subrequest的使用方式
参考资料<深入理解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模块完整代码
1 #include <ngx_config.h> 2 #include <ngx_core.h> 3 #include <ngx_http.h> 4 5 //hq.sinajs.cn/list=s_sh000001 6 7 //请求上下文 8 typedef struct { 9 ngx_str_t stock[6]; 10 } ngx_http_mytest_ctx_t; 11 12 static char * 13 ngx_http_mytest(ngx_conf_t *cf,ngx_command_t *cmd,void *conf); 14 static ngx_int_t 15 ngx_http_mytest_handler(ngx_http_request_t *r); 16 static void 17 mytest_post_handler(ngx_http_request_t *r); 18 19 20 static ngx_command_t ngx_http_mytest_commands[]={ 21 { 22 //配置项名称 23 ngx_string("mytest"), 24 //配置项类型(可出现的位置,参数的个数) 25 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, 26 //出现了name中指定的配置项后,将会调用该方法处理配置项的参数 27 ngx_http_mytest, 28 NGX_HTTP_LOC_CONF_OFFSET, 29 0, 30 NULL 31 }, 32 ngx_null_command 33 }; 34 35 static char * 36 ngx_http_mytest(ngx_conf_t *cf,ngx_command_t *cmd,void *conf) 37 { 38 //找到mytest配置项所属的配置块 39 ngx_http_core_loc_conf_t *clcf; 40 clcf=ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module); 41 /* 42 HTTP框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时 43 如果请求的主机域名、URI与mytest配置项所在的配置块相匹配, 44 就将调用我们事先的ngx_http_mytest_handler方法处理这个请求 45 */ 46 clcf->handler=ngx_http_mytest_handler; 47 return NGX_CONF_OK; 48 } 49 50 static ngx_http_module_t ngx_http_mytest_module_ctx={ 51 NULL, 52 NULL, 53 NULL, 54 NULL, 55 NULL, 56 NULL, 57 NULL, 58 NULL 59 }; 60 61 ngx_module_t ngx_http_mytest_module={ 62 NGX_MODULE_V1, 63 //指向ngx_http_module_t结构体 64 &ngx_http_mytest_module_ctx, 65 //用来处理nginx.conf中的配置项 66 ngx_http_mytest_commands, 67 //表示该模块的类型 68 NGX_HTTP_MODULE, 69 NULL, 70 NULL, 71 NULL, 72 NULL, 73 NULL, 74 NULL, 75 NULL, 76 NGX_MODULE_V1_PADDING 77 }; 78 79 80 //子请求结束时的回调方法 81 static ngx_int_t mytest_subrequest_post_handler(ngx_http_request_t *r, 82 void *data,ngx_int_t rc) 83 { 84 //获取父请求 85 ngx_http_request_t *pr=r->parent; 86 //获取上下文 87 ngx_http_mytest_ctx_t *myctx=ngx_http_get_module_ctx(pr,ngx_http_mytest_module); 88 89 pr->headers_out.status=r->headers_out.status; 90 //查看返回码,如果为NGX_HTTP_OK,则意味访问成功,接着开始解析HTTP包体 91 if(r->headers_out.status==NGX_HTTP_OK) 92 { 93 int flag=0; 94 //上游响应会保存在buffer缓冲区中 95 ngx_buf_t *pRecvBuf=&r->upstream->buffer; 96 /* 97 解析上游服务器的相应,并将解析出的值赋到上下文结构体myctx->stock数组中 98 新浪服务器的返回大致如下: 99 var hq_str_s_sh000009=" 上证 380,3356.355,-5.725,-0.17,266505,2519967" 100 */ 101 for(;pRecvBuf->pos!=pRecvBuf->last;pRecvBuf->pos++) 102 { 103 if(*pRecvBuf->pos==','||*pRecvBuf->pos=='\"') 104 { 105 if(flag>0) 106 { 107 myctx->stock[flag-1].len=pRecvBuf->pos-myctx->stock[flag-1].data; 108 } 109 flag++; 110 myctx->stock[flag-1].data=pRecvBuf->pos+1; 111 } 112 if(flag>6) 113 break; 114 } 115 } 116 //设置父请求的回调方法 117 pr->write_event_handler=mytest_post_handler; 118 return NGX_OK; 119 } 120 121 //父请求的回调方法 122 static void 123 mytest_post_handler(ngx_http_request_t *r) 124 { 125 //如果没有返回200,则直接把错误码发送回用户 126 if(r->headers_out.status!=NGX_HTTP_OK) 127 { 128 ngx_http_finalize_request(r,r->headers_out.status); 129 return; 130 } 131 //取出上下文 132 ngx_http_mytest_ctx_t *myctx=ngx_http_get_module_ctx(r,ngx_http_mytest_module); 133 //定义发给用户的HTTP包体内容 134 ngx_str_t output_format=ngx_string("stock[%V],Today current price:%V,volumn:%V"); 135 //计算待发送包体的长度 136 int bodylen=output_format.len+myctx->stock[0].len+ 137 myctx->stock[1].len+myctx->stock[4].len-6; 138 r->headers_out.content_length_n=bodylen; 139 //在内存池上分配内存以保存将要发送的包体 140 ngx_buf_t *b=ngx_create_temp_buf(r->pool,bodylen); 141 ngx_snprintf(b->pos,bodylen,(char *)output_format.data, 142 &myctx->stock[0],&myctx->stock[1],&myctx->stock[4]); 143 b->last=b->pos+bodylen; 144 b->last_buf=1; 145 146 ngx_chain_t out; 147 out.buf=b; 148 out.next=NULL; 149 //设置Content-Type 150 static ngx_str_t type=ngx_string("text/plain;charset=GBK"); 151 r->headers_out.content_type=type; 152 r->headers_out.status=NGX_HTTP_OK; 153 154 r->connection->buffered|=NGX_HTTP_WRITE_BUFFERED; 155 ngx_int_t ret=ngx_http_send_header(r); 156 ret=ngx_http_output_filter(r,&out); 157 158 ngx_http_finalize_request(r,ret); 159 } 160 161 //启动subrequest 162 static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r) 163 { 164 //创建HTTP上下文 165 ngx_http_mytest_ctx_t *myctx=ngx_http_get_module_ctx(r,ngx_http_mytest_module); 166 if(myctx==NULL) 167 { 168 myctx=ngx_palloc(r->pool,sizeof(ngx_http_mytest_ctx_t)); 169 if(myctx==NULL) 170 { 171 return NGX_ERROR; 172 } 173 ngx_http_set_ctx(r,myctx,ngx_http_mytest_module); 174 } 175 //ngx_http_post_subrequest_t结构体会决定子请求的回调方法 176 ngx_http_post_subrequest_t *psr=ngx_palloc(r->pool,sizeof(ngx_http_post_subrequest_t)); 177 if(psr==NULL){ 178 return NGX_HTTP_INTERNAL_SERVER_ERROR; 179 } 180 //设置子请求回调方法为mytest_subrequest_post_handler 181 psr->handler=mytest_subrequest_post_handler; 182 //将data设为myctx上下文,这样回调mytest_subrequest_post_handler时传入的data参数就是myctx 183 psr->data=myctx; 184 //子请求的URI前缀是/list 185 ngx_str_t sub_prefix=ngx_string("/list="); 186 ngx_str_t sub_location; 187 sub_location.len=sub_prefix.len+r->args.len; 188 sub_location.data=ngx_palloc(r->pool,sub_location.len); 189 ngx_snprintf(sub_location.data,sub_location.len, 190 "%V%V",&sub_prefix,&r->args); 191 //sr就是子请求 192 ngx_http_request_t *sr; 193 //调用ngx_http_subrequest创建子请求 194 ngx_int_t rc=ngx_http_subrequest(r,&sub_location,NULL,&sr,psr,NGX_HTTP_SUBREQUEST_IN_MEMORY); 195 if(rc!=NGX_OK){ 196 return NGX_ERROR; 197 } 198 return NGX_DONE; 199 200 }
配置好该模块后,利用telnet模拟HTTP请求得到下图
对比与直接访问hq.sinajs.cn/list=s_sh000001
该模块将客户的请求转换成子请求发送个hq.sinajs.cn,然后将其响应的信息修改之后发送给客户。