Nginx 过滤模块的分析( 二 )


具体模块的响应体过滤函数的格式类似这样:
static intngx_http_example_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ... return ngx_http_next_body_filter(r, in); }该函数的返回值一般是 NGX_OK,NGX_ERROR 和 NGX_AGAIN,分别表示处理成功,失败和未完成 。
主要功能介绍
响应的主体内容就存于单链表 in,链表一般不会太长,有时 in 参数可能为 NULL 。in中存有buf结构体中,对于静态文件,这个buf大小默认是 32K;对于反向代理的应用,这个buf可能是4k或者8k 。为了保持内存的低消耗,Nginx一般不会分配过大的内存,处理的原则是收到一定的数据,就发送出去 。一个简单的例子,可以看看Nginx的chunked_filter模块,在没有 content-length 的情况下,chunk 模块可以流式(stream)的加上长度,方便浏览器接收和显示内容 。
在响应体过滤模块中,尤其要注意的是 buf 的标志位,完整描述可以在“相关结构体”这个节中看到 。如果 buf 中包含 last 标志,说明是最后一块 buf,可以直接输出并结束请求了 。如果有 flush 标志,说明这块 buf 需要马上输出,不能缓存 。如果整块 buffer 经过处理完以后,没有数据了,你可以把 buffer 的 sync 标志置上,表示只是同步的用处 。
当所有的过滤模块都处理完毕时,在最后的 write_fitler 模块中,Nginx 会将 in 输出链拷贝到 r->out 输出链的末尾,然后调用 sendfile 或者 writev 接口输出 。由于 Nginx 是非阻塞的 socket 接口,写操作并不一定会成功,可能会有部分数据还残存在 r->out 。在下次的调用中,Nginx 会继续尝试发送,直至成功 。
发出子请求
Nginx 过滤模块一大特色就是可以发出子请求,也就是在过滤响应内容的时候,你可以发送新的请求,Nginx 会根据你调用的先后顺序,将多个回复的内容拼接成正常的响应主体 。一个简单的例子可以参考 addition 模块 。
Nginx 是如何保证父请求和子请求的顺序呢?当 Nginx 发出子请求时,就会调用 ngx_http_subrequest 函数,将子请求插入父请求的 r->postponed 链表中 。子请求会在主请求执行完毕时获得依次调用 。子请求同样会有一个请求所有的生存期和处理过程,也会进入过滤模块流程 。
关键点是在 postpone_filter 模块中,它会拼接主请求和子请求的响应内容 。r->postponed 按次序保存有父请求和子请求,它是一个链表,如果前面一个请求未完成,那后一个请求内容就不会输出 。当前一个请求完成时并输出时,后一个请求才可输出,当所有的子请求都完成时,所有的响应内容也就输出完毕了 。
一些优化措施
Nginx 过滤模块涉及到的结构体,主要就是 chain 和 buf,非常简单 。在日常的过滤模块中,这两类结构使用非常频繁,Nginx采用类似 freelist 重复利用的原则,将使用完毕的 chain 或者 buf 结构体,放置到一个固定的空闲链表里,以待下次使用 。
比如,在通用内存池结构体中,pool->chain 变量里面就保存着释放的 chain 。而一般的 buf 结构体,没有模块间公用的空闲链表池,都是保存在各模块的缓存空闲链表池里面 。对于 buf 结构体,还有一种 busy 链表,表示该链表中的 buf 都处于输出状态,如果 buf 输出完毕,这些 buf 就可以释放并重复利用了 。
功能函数名chain 分配ngx_alloc_chain_linkchain 释放ngx_free_chainbuf 分配ngx_chain_get_free_bufbuf 释放ngx_chain_update_chains
过滤内容的缓存
由于 Nginx 设计流式的输出结构,当我们需要对响应内容作全文过滤的时候,必须缓存部分的 buf 内容 。该类过滤模块往往比较复杂,比如 sub,ssi,gzip 等模块 。这类模块的设计非常灵活,我简单讲一下设计原则:

  1. 输入链 in 需要拷贝操作,经过缓存的过滤模块,输入输出链往往已经完全不一样了,所以需要拷贝,通过 ngx_chain_add_copy 函数完成 。
  2. 一般有自己的 free 和 busy 缓存链表池,可以提高 buf 分配效率 。
  3. 如果需要分配大块内容,一般分配固定大小的内存卡,并设置 recycled 标志,表示可以重复利用 。
  4. 原有的输入 buf 被替换缓存时,必须将其 buf->pos 设为 buf->last,表明原有的 buf 已经被输出完毕 。或者在新建立的 buf,将 buf->shadow 指向旧的 buf,以便输出完毕时及时释放旧的 buf 。




推荐阅读