缘由
今天阅读了深入理解nginx的第五章的后半部分,主要讲的就是subrequest,功能我觉得主要就是两点:- 具备类似代理的功能,就是把一个请求转发到上游服务器,但是与upstream的不同之处主要在于upstream只能把上游服务器的内容原封不动的返还给客户端。但是subrequest可以把上游服务器的相应在代理服务器这边做一定的修改之后再返回给上游服务器
- 由substream可以将一个请求分解成多个子请求,每一个子请求完成一个功能。结合功能一来看,那就是将多个子请求返回的结果在代理服务器这边加工之后返回给客户端。
实现subrequest
例子
我们让我们的nginx作为一个代理服务器去访问,新浪的股票网站的查阅股价的页面,然后我们在我们的服务器内输入需要查询的股票号码,nginx会去新浪查询,然后把结果返回给我们,结果是经过我们修饰的,所以看起来非常简单和简洁。这体现出我们可以从很多别的服务器拿到东西,然后自己组装。图如下所示:配置文件
我们首先就是要说清楚,nginx作为代理服务器会替我们访问哪个网站。作如下配置: location /list {
proxy_pass http://hq.sinajs.cn;//访问的上游服务器
proxy_set_header Accept-Encoding "";//要求上游服务器不要使用gzip压缩
}
我们可以单独的以下面的格式访问一下:http://hq.sinajs.cn/list=s_sh000001
如下图所示,注意与我们localhost返回的结果是不同的,证明我们做了摘取处理。而具体为什么新浪会返回这个,我觉不知道了。
我们这次也改了一下localhost的URI,如下,这样还是会访问到mytest模块:
location /query {
mytest;
}
流程说明
经过前几节的说明,我们都知道是如何开始执行 ngx_http_mytest_handler()函数的了,那么为了完成这次功能,主要的变化也在 ngx_http_mytest_handler()函数内。到了 ngx_http_mytest_handler()函数后,我们将会依次执行以下几个主要函数:- ngx_http_subrequest():初始化substream功能,而且我认为也向上游服务器发送了请求(子请求)
- mytest_subrequest_post_handler():子请求接受到响应后调用的回调函数,会从响应中解析出我们需要的东西,所以这个函数是我们自己实现的。
- mytest_post_handler():当上面这个回调函数完成后,就会调用这个回调函数,我认为这个回调函数最重要的功能就是向客户端返回我们我们的子请求找到的信息。很显然,如果有很多个子请求,那么也肯定会在这个函数内,将各个子请求返回的信息组装起来。所以这个函数也是我们自己实现的,因为
模块的上下文
这次我们使用模块的上下文来存储股票的信息,如下所示:typedef struct
{
ngx_str_t stock[6];
} ngx_http_mytest_ctx_t;
具体实现
ngx_http_mytest_handler()函数开始,注意我 删除了很多无关代码,只是为了体现该函数主要的做法: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);
// ngx_http_post_subrequest_t结构体会决定子请求的回调方法,参见5.4.1节
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设为myctx上下文,这样回调mytest_subrequest_post_handler
//时传入的data参数就是myctx
psr->data = myctx;
//子请求的URI前缀是/list,这是因为访问新浪服务器的请求必须是类
//似/list=s_sh000001这样的URI,这与5.6.1节在nginx.conf中
//配置的子请求location中的URI是一致的
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);//zy:r->args
//就是我们的http://localhost/query?s_sh000001
//问号后面的部分,现在都存储在了sub_location.data里面
// 和/list= 组成在了一起
//sr就是子请求
ngx_http_request_t *sr;
//调用ngx_http_subrequest创建子请求,它只会返回NGX_OK
//或者NGX_ERROR。返回NGX_OK时,sr就已经是合法的子请求。注意,这里
//的NGX_HTTP_SUBREQUEST_IN_MEMORY参数将告诉upstream模块把上
//游服务器的响应全部保存在子请求的sr->upstream->buffer内存缓冲区中
ngx_int_t rc = ngx_http_subrequest(r, &sub_location, NULL, &sr, psr, NGX_HTTP_SUBREQUEST_IN_MEMORY);
...
//必须返回NGX_DONE,理由同upstream
return NGX_DONE;
}
mytest_subrequest_post_handler函数,同样删除了很多无关代码:
static ngx_int_t mytest_subrequest_post_handler(ngx_http_request_t *r,
void *data, ngx_int_t rc)
{
//当前请求r是子请求,它的parent成员就指向父请求
ngx_http_request_t *pr = r->parent;
//注意,上下文是保存在父请求中的(参见5.6.5节),所以要由pr中取上下文。
//其实有更简单的方法,即参数data就是上下文,初始化subrequest时
//我们就对其进行设置了的,这里仅为了说明如何获取到父请求的上下文
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(也就是200)意味着访问新浪服务器成功,接着将
//开始解析http包体
if (r->headers_out.status == NGX_HTTP_OK)
{
int flag = 0;
//在不转发响应时,buffer中会保存着上游服务器的响应。特别是在使用
//反向代理模块访问上游服务器时,如果它使用upstream机制时没有重定义
//input_filter方法,upstream机制默认的input_filter方法会试图
//把所有的上游响应全部保存到buffer缓冲区中
ngx_buf_t* pRecvBuf = &r->upstream->buffer;
//以下开始解析上游服务器的响应,并将解析出的值赋到上下文结构体
//myctx->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;
}
mytest_post_handler函数:
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包体内容,格式为:
//stock[…],Today current price: …, volumn: …
ngx_str_t output_format = ngx_string("stock[%V],Today current price: %V, volumn: %V");
//计算待发送包体的长度,为什么是stock[0]、stock[1]、stock[4],只是作者刚好想
//取这个几个值而已
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;
ngx_int_t ret = ngx_http_send_header(r);
ret = ngx_http_output_filter(r, &out);
//注意,这里发送完响应后必须手动调用ngx_http_finalize_request
//结束请求,因为这时http框架不会再帮忙调用它
ngx_http_finalize_request(r, ret);
}
总结
作为第一遍看代码,主要不是在细节上考量,而是整体上把握一下功能,大概的流程。这是我现在项目。那么目前作为一个substream的例子,我算看明白其用途和流程了。
源代码
来自书中给的源代码,可以在:
陶辉的分享网站 上下载,本次是第五章