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