<深入理解Nginx模块开发与架构解析>--陶辉
upstream
1,upstream与subrequest的作用范围:如果希望把第三方服务的内容几乎原封不动的返回给用户,一般使用upstream方式,如果访问第三方服务只是为了获取某些信息,在根据这些信息构造响应则应采取subrequest方式。
2,upstream机制的使用关键在于设置ngx_http_request_t的upstream成员,包括 a,upstream的配置信息:
一般将ngx_http_upstream_conf_t封装在自己模块自定义的配置结构体中,可以采用预定义的配置解析函数进行获取配置参数信息。
b,三个必须设置的回调函数:
ngx_int_t
(*create_request) (ngx_http_request_t *r);
ngx_int_t
(*process_header) (ngx_http_request_t *r);
ngx_int_t
(*finalize_request) (ngx_http_request_t *r);
必须设置的原因在于nginx会调用对应的函数指针,如果不设置的话初始化为 NULL,会出现调用空指针的错误。
c,上游服务器地址:
ngx_http_upstream_resolved_t
*resolved;
d,启动upstream
调用ngx_http_upstream_init方法,需要对r->main->count引用计数加1,这是在告诉HTTP框架将当前请求的引用计数加1,即告诉ngx_http_mytest_handler方法暂时不要销毁请求,因为HTTP框架只有在引用计数为0时才会真正的销毁请求。返回NGX_DONE告诉HTTP框架暂停执行请求的下一阶段。
上述步骤结合起来就构成了通过upstream访问第三方服务器的模块。
3,在process_header和handler函数中都需要设置上下文,HTTP框架提供了ngx_http_status_t结构体作为上下文,使用时将其封装在自己定义的上下文结构体中,作为唯一的结构体成员。
4,回调方法的执行:
create_request只会被调用1次,而reinit_request则可能被调用多次,被调用的原因就是在第一次想上游服务器建立连接时失败。
finalize_request可以不做任何事情,但必须实现,否则会出现空指针调用的严重错误。
process_header用于解析上游服务器返回的基于TCP的响应头部,因此,它可能被调用多次,调用次数与process_header的返回值有关,如果返回NGX_AGAIN,说明没有接收到完整的响应头部,当再次接收到上游的TCP流时,还会将其当作TCP头部,调用process_header进行处理。
5,模块实现
配置项参数,每一个HTTP请求会有一个独立的ngx_http_upstream_conf_t结构体,本模块中所有请求共享一个ngx_http_upstream_conf_t结构体。
请求上下文,解析HTTP响应行时可以使用HTTP框架提供的ngx_http_status_t结构。需要请求上下文的原因在于,upstream模块每次接收到一段TCP流时都会回调mytest模块实现的process_header方法,需要有一个上下文保存解析状态。三个必须设置的回调函数中只有process_header是可能被多次回调的。
create_request,构造发往上游服务器的请求采用ngx_buf_t动态分配内存形式,一是向上游发送的请求可能会经过epoll多次调用,这时必须保证该请求始终有效,二是请求结束时内存会被自动释放,降低了内存泄漏。将ngx_alloc_chain_link返回的指针赋值给r->upstream->request_bufs,request_bufs成员是决定向上游发送什么样的请求,它是一个ngx_chain_t结构体。最后就是设置r->upstream的状态位,并将
r->header_hash赋值为1,不能为0。
process_header,负责解析上游服务器发来的基于TCP的包头。本模块中就是解析HTTP响应行和HTTP头部。模块使用
mytest_process_status_line和
mytest_upstream_process_header
解析HTTP响应行和HTTP响应头部。两个方法是通用的,适用于解析所有的HTTP响应包。之所以使用两个方法解析包头,主要是无论是响应行还是响应头部都是不定长的,需要使用状态机来解析。HTTP框架分别提供了
ngx_http_parse_status_line和
ngx_http_parse_header_line
来解析HTTP响应行和HTTP响应头部。当解析到完整的HTTP响应行时,会将解析出的信息设置到r->upstream->headers_in结构体中。当upstream解析完所有的包头时,会把headers_in中的成员设置到将要向下游发送的r->headers_out结构体中。当解析完HTTP响应头部时,简单的将上游服务器发送的HTTP头部添加到请求
r->upstream->headers_in.headers链表中即可。如果有需要特殊处理的HTTP头部,需要在mytest_upstream_process_header中进行。
finalize_request,本模块中没有需要释放的资源,简单做一个日志记录用于调试,NGX_LOG_DEBUG。
ngx_http_mytest_handler,在模块主方法中建立HTTP上下文结构体,仅调用一次ngx_http_upstream_create方法初始化r->upstream成员,获取配置结构体并赋值
r->upstream->conf成员,设置上游服务器地址,设置3个回调方法,将r->main->count加1,调用ngx_http_upstream_init启动upstream,完毕。
subrequest
1,与upstream的关系
a,只要不是完全将上游服务器的响应包体转发到下游客户端,基本上都会使用subquest创建出子请求,并由子请求使用upstream机制访问上游服务器,然后由父请求根据上游响应重新构造返回给下游客户端的响应。
b,在HTTP框架的设计上,二者是密切相关的。
2,使用步骤
a,在nginx.conf文件中配置好子请求的处理方式
b,启动subquest子请求
c,实现子请求处理完毕时的回调方法
d,实现父请求被激活时的回调方法
子请求与普通请求的不同之处在于,子请求是由父请求生成的,不是接收客户端发来的网络包再由HTTP框架解析出的。配置处理子请求的模块与普通请求完全一样,可以使用任意HTTP官方模块,第三方模块来处理。
3,两个回调方法
子请求处理完毕时回调方法对应指针:
typedef
ngx_int_t (*ngx_http_post_subrequest_pt) (ngx_http_request_t *r,
void
*data, ngx_int_t rc);
在该回调方法中必须设置另一个回调方法,即父请求被激活的处理方法,也就是父请求ngx_http_request_t的write_event_handler成员。父请求ngx_http_request_t可以通过子请求ngx_http_request_t
*r的r->parent成员获得。
父请求被重新激活后回调方法对应指针:
typedef
void (*ngx_http_event_handler_pt) (ngx_http_request_t*
r);
4,启动subrequest
调用
ngx_int_t
ngx_http_subrequest(ngx_http_request_t
*r, ngx_str_t *rui, ngx_str_t *args,
ngx_http_request_t
**psr, ngx_http_post_subrequest_t *ps,
ngx_uint_t
flags);
方法建立子请求。
转发上游响应
在不转发响应时,upstream会将上游响应全部保存到r->upstream->buffer中,buffer是一个ngx_buf_t结构体。
子请求结束后如何激活父请求
当事件模块检测到网络关闭事件而这个请求的处理方法属于upstream模块时,调用ngx_http_upstream_finalize_request方法就诶书upstream机制下的请求。它会检查当前请求是否是子请求,是的话就调用我们写的回调函数mytest_subrequest_post_handler,子请求的回调执行完毕之后,finalize_request继续执行直到完毕,然后HTTP框架发现当前请求还有父请求需要执行,则调用父请求的write_event_handler回调方法。
详见P193