nginx模块开发入门(七)-3.2 Anatomy of an Upstream (a.k.a Proxy) Handler

[size=large]3.2. 剖析Upstream(又称 Proxy) Handler
Anatomy of an Upstream (a.k.a Proxy) Handler[/size]


我已经帮你了解了如何让你的handler来产生响应。有些时候你可以用一小段C代码就可以得到响应,但是通常情况下你需要同另外一台server打交道(比如你正在写一个用来实现某种网络协议的模块)。你当然可以自己实现一套网络编程的东东,但是如果你只收到部分的响应,需要等待余下的响应数据,你会怎么办?你不会想阻塞整个事件处理循环吧?这样会毁掉Nginx的良好性能!幸运的是,Nginx允许你在它处理后端服务器(叫做"upstreams")的机制上加入你的回调函数,因此你的模块将可以和其他的server通信,同时还不会妨碍其他的请求。这一节将介绍模块如何和一个upstream(如 Memcached, FastCGI,或者其它HTTP server)通信。

[b]3.2.1. Upstream 回调函数概要[/b]

与其他模块的回调处理函数不一样,upstream模块的处理函数几乎不做“实事”。它压根不调用[color=blue]ngx_http_output_filter[/color]。它仅仅是告诉回调函数什么时候可以向upstream server写数据了,以及什么时候能从upstream server读数据了。实际上它有6个可用的钩子:
[color=blue]create_request[/color] 生成发送到upstream server的request buffer(或者一条缓冲链)
[color=blue]reinit_request[/color] 在与后端服务器连接被重置的情况下(在[color=blue]create_request[/color] 被第二次调用之前)被调用
[color=blue]process_header[/color] 处理upstream 响应的第一个bit,通常是保存一个指向upstream "payload"的指针
[color=blue]abort_request[/color] 在客户端放弃请求时被调用
[color=blue]finalize_request[/color] 在Nginx完成从upstream读取数据后调用
[color=blue]input_filter[/color] 这是一个消息体的filter,用来处理响应消息体(例如把尾部删除)

这些钩子是怎么勾上去的呢?下面是一个例子,简单版本的代理模块处理函数:

static ngx_int_t
ngx_http_proxy_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_http_upstream_t *u;
ngx_http_proxy_loc_conf_t *plcf;

plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);

/* set up our upstream struct */
u = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_t));
if (u == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

u->peer.log = r->connection->log;
u->peer.log_error = NGX_ERROR_ERR;

u->output.tag = (ngx_buf_tag_t) &ngx_http_proxy_module;

u->conf = &plcf->upstream;

/* attach the callback functions */
u->create_request = ngx_http_proxy_create_request;
u->reinit_request = ngx_http_proxy_reinit_request;
u->process_header = ngx_http_proxy_process_status_line;
u->abort_request = ngx_http_proxy_abort_request;
u->finalize_request = ngx_http_proxy_finalize_request;

r->upstream = u;

rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);

if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
return rc;
}

return NGX_DONE;
}

看上去都是些例行事务,不过重要的是那些回调函数。同时还要注意的是[color=blue]ngx_http_read_client_request_body[/color],它又设置了一个回调函数,在Nginx完成从客户端读数据后会被调用。

这些个回调函数都要做些什么工作呢?通常情况下,[color=blue]reinit_request[/color], [color=blue]abort_request[/color], 和 [color=blue]finalize_request[/color]用来设置或重置一些内部状态,但这些都是几行代码的事情。真正做苦力的是[color=blue]create_request[/color] 和 [color=blue]process_header[/color]。

[b]3.2.2. create_request 回调函数[/b]

简单起见,假设我有一个upstream server,它读入一个字符打印出两个字符。那么函数应该如何来写呢?

create_request需要申请一个buffer来存放“一个字符”的请求,为buffer申请一个链表,并且把链表挂到upstream结构体上。看起来就像这样:

static ngx_int_t
ngx_http_character_server_create_request(ngx_http_request_t *r)
{
/* make a buffer and chain */
ngx_buf_t *b;
ngx_chain_t *cl;

b = ngx_create_temp_buf(r->pool, sizeof("a") - 1);
if (b == NULL)
return NGX_ERROR;

cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL)
return NGX_ERROR;

/* hook the buffer to the chain */
cl->buf = b;
/* chain to the upstream */
r->upstream->request_bufs = cl;

/* now write to the buffer */
b->pos = "a";
b->last = b->pos + sizeof("a") - 1;

return NGX_OK;
}

不是很难,对吧?当然实际应用中你很可能还会用到请求里面的URI。[color=blue]r->uri[/color]作为一个 [color=blue]ngx_str_t[/color]类型也是有效的,GET的参数在[color=blue]r->args[/color]中,最后别忘了你还能访问请求的header和 cookie信息。

[b]3.2.3. process_header 回调函数[/b]

现在轮到[color=blue]process_header[/color]了,就像[color=blue]create_request[/color]把链表指针挂到请求结构体上去一样,[color=blue]process_header[/color]把响应指针移到客户端可以接收到的部分。同时它还会从upstream 读入header信息,并且相应的设置发往客户端的响应headers。

这里有个小例子,读进两个字符的响应。我们假设第一个字符代表“状态”字符。如果它是问号,我们将返回一个404错误并丢弃剩下的那个字符。如果它是空格,我们将以 200 OK的响应把另一个字符返回给客户端。好吧,这不是什么多有用的协议,不过可以作为一个不错的例子。那么我们如何来实现这个[color=blue]process_header[/color] 函数呢?

static ngx_int_t
ngx_http_character_server_process_header(ngx_http_request_t *r)
{
ngx_http_upstream_t *u;
u = r->upstream;

/* read the first character */
switch(u->buffer.pos[0]) {
case '?':
r->header_only; /* suppress this buffer from the client */
u->headers_in.status_n = 404;
break;
case ' ':
u->buffer.pos++; /* move the buffer to point to the next character */
u->headers_in.status_n = 200;
break;
}

return NGX_OK;
}

就是这样。操作头部,改变指针,搞定!注意[color=blue]headers_in[/color]实际上就是我们之前提到过的头部结构体( [url=http://lxr.evanmiller.org/http/source/http/ngx_http_request.h#L158]http/ngx_http_request.h[/url]),但是它位于来自upstream的头中。一个真正的代理模块会在头信息的处理上做很多文章,不光是错误处理,做什么完全取决于你的想法。

但是……如果一个buffer没有能够装下全部的从upstream来的头信息,该怎么办呢?

[b]3.2.4. 状态保持 (Keeping state)[/b]

好了,还记得我说过[color=blue]abort_request[/color], [color=blue]reinit_request[/color]和[color=blue]finalize_request[/color] 可以用来重置内部状态吗?这是因为许多upstream模块都有其内部状态。模块需要定义一个 “自定义上下文结构” ,来标记目前为止从upstream读到了什么。这跟之前说的“模块上下文”不是一个概念。“模块上下文”是预定义类型,而“自定义上下文结构”可以包含任何你需要的数据和字段(这可是你自己定义的结构体)。这个结构体在[color=blue]create_request[/color]函数中被实例化,大概像这样:

ngx_http_character_server_ctx_t *p; /* my custom context struct */

p = ngx_pcalloc(r->pool, sizeof(ngx_http_character_server_ctx_t));
if (p == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

ngx_http_set_ctx(r, p, ngx_http_character_server_module);


最后一行实际上将“自定义上下文结构体”注册到了特定的请求和模块名上,以便在稍后取用。当你需要这个结构体时(可能所有的回调函数中都需要它),只需要:

ngx_http_proxy_ctx_t *p;
p = ngx_http_get_module_ctx(r, ngx_http_proxy_module);


指针 p 可以得到当前的状态. 设置、重置、增加、减少、往里填数据……你可以随心所欲的操作它。当upstream服务器返回一块一块的响应时,读取这些响应的过程中使用持久状态机是个很nx的办法,它不用阻塞主事件循环。很好很强大!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值