phase handler处理中介绍了content handler用于产生响应内容,随便找一个content phase的模块,比如:ngx_http_static_module.c,会发现在content handler中会调用ngx_http_send_header,然后最后调用ngx_http_output_filter。这两个函数就是发送响应头部和响应体的,在nginx中输出内容是通过filter完成的。
filter模块用于过滤和输出响应内容,nginx将所有的filter组织成只有头结点的单链表(实际上就是栈),这个头结点分别是ngx_http_top_header_filter(过滤头部)和ngx_http_top_body_filter(过滤响应体)。在每次初始化一个filter模块时,都会将当前filter链表的头结点ngx_http_top_header_filter和ngx_http_top_body_filter分别保存为ngx_http_next_header_filter和ngx_http_next_body_filter,同时将本模块的filter函数保存为ngx_http_top_header_filter和ngx_http_top_body_filter,然后在本模块filter函数的最后部分调用ngx_http_next_header_filter和ngx_http_next_body_filter,这样就实现了所有filter模块的链式调用。由于filter模块的添加实际上就是不停的向链表的头push节点,所以后添加的模块比先添加的模块在filter链中靠前。
先看一下ngx_http_send_header函数:
ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
if (r->err_status) {
r->headers_out.status = r->err_status;
r->headers_out.status_line.len = 0;
}
return ngx_http_top_header_filter(r);
}
实现很简单,就是调用ngx_http_top_header_filter函数,然后这个函数后调用接下来的filter直到filter链遍历完为止。ngx_http_output_filter与之类似,调用ngx_http_top_body_filter。
filter的添加都是在http模块的postconfiguration回调函数中完成的,这部分调用是在ngx_http_block中:
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = ngx_modules[m]->ctx;
if (module->postconfiguration) {
if (module->postconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
回调函数的调用顺序是遍历ngx_modules数组,然后逐一调用postconfiguration函数,那么filter模块在ngx_modules数组中的顺序就决定了其filter函数在filter链中位置。下面是ngx_modules数组的内容:
ngx_module_t *ngx_modules[] = {
&ngx_core_module,
//……
&ndk_http_module,
&ngx_http_write_filter_module,
&ngx_http_header_filter_module,
&ngx_http_chunked_filter_module,
&ngx_http_range_header_filter_module,
&ngx_http_gzip_filter_module,
&ngx_http_postpone_filter_module,
&ngx_http_ssi_filter_module,
&ngx_http_charset_filter_module,
&ngx_http_userid_filter_module,
&ngx_http_headers_filter_module,
&ngx_http_set_misc_module,
&ngx_http_echo_module,
&ngx_http_copy_filter_module,
&ngx_http_range_body_filter_module,
&ngx_http_not_modified_filter_module,
NULL
};
可以看到ngx_http_write_filter_module和ngx_http_header_filter_module两个模块是最靠前的两个filter模块,所以它们处于filter链的最末尾,也就是最后执行。这两个模块就是用来输出header和body的,下面具体分析一下ngx_http_write_filter_module,ngx_http_header_filter_module与之类似。
ngx_http_write_filter_module模块用于输出响应体,在调用时需要传入两个参数ngx_request_t和ngx_chain_t,第一个就是请求,第二个参数是输出内容的buffer链表,通过buffer的last字段标识最后一个buffer。这个模块的filter函数是ngx_http_write_filter,完成的功能很简单就是遍历buffer链表,然后输出响应内容。这里还需要注意一点,ngx_request_t有个out字段用来保存上一次没有发送的chain,当接收到新chain时,需要将新chain连接到旧chain。下面看一下代码。
c = r->connection;
if (c->error) {
return NGX_ERROR;
}
size = 0; /* 待输出的内容的大小 */
flush = 0; /* 是否需要flush */
last = 0; /* 是否是最后一个buffer */
ll = &r->out; /* 保存上次没有输出的ngx_buf_t */
这里对使用的变量初始化。
/* find the size, the flush point and the last link of the saved chain */
for (cl = r->out; cl; cl = cl->next) {
ll = &cl->next;
ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0,
"write old buf t:%d f:%d %p, pos %p, size: %z "
"file: %O, size: %z",
cl->buf->temporary, cl->buf->in_file,
cl->buf->start, cl->buf->pos,
cl->buf->last - cl->buf->pos,
cl->buf->file_pos,
cl->buf->file_last - cl->buf->file_pos);
#if 1
// ……
#endif
size += ngx_buf_size(cl->buf);
if (cl->buf->flush || cl->buf->recycled) {
flush = 1;
}
if (cl->buf->last_buf) {
last = 1;
}
}
这段代码统计上次没有发送的chain的大小,以及是否需要flush还有是不是最后一个buffer。
/* add the new chain to the existent one */
for (ln = in; ln; ln = ln->next) {
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NGX_ERROR;
}
cl->buf = ln->buf;
/* ll原本保存的是old buf的最后一个节点的next的地址,这里会将new buf连接到old buf后 */
*ll = cl;
ll = &cl->next;
ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0,
"write new buf t:%d f:%d %p, pos %p, size: %z "
"file: %O, size: %z",
cl->buf->temporary, cl->buf->in_file,
cl->buf->start, cl->buf->pos,
cl->buf->last - cl->buf->pos,
cl->buf->file_pos,
cl->buf->file_last - cl->buf->file_pos);
#if 1
// ……
#endif
size += ngx_buf_size(cl->buf);
if (cl->buf->flush || cl->buf->recycled) {
flush = 1;
}
/* 需要输出的最后一个buf */
if (cl->buf->last_buf) {
last = 1;
}
}
将新chain添加的旧chain后,并统计size、flush和last。
接下来就是对chain进行处理,这会涉及一些标记,先来处理一下它们:
1. clcf->postpone_output:由于处理postpone_output指令,用于设置延时输出的阈值。比如指令“postpone s”,当输出内容的size小于s,并且不是最后一个buffer,也不需要flush,那么就延时输出。
2. c->write->delayed:表示当前连接是否因为某种原因需要延时输出,比如超过发送速率限制等,只有在其他地方取消delayed标记后才能继续输出。在delayed设置的情况下,需要设置c->buffered,并返回NGX_AGAIN。
3. c->buffered:在buffer没有输出完的情况下,标记具体在哪个模块被buffer住了。buffered的取值:
#define NGX_HTTP_LOWLEVEL_BUFFERED 0xf0
#define NGX_HTTP_WRITE_BUFFERED 0x10
#define NGX_HTTP_GZIP_BUFFERED 0x20
#define NGX_HTTP_SSI_BUFFERED 0x01
#define NGX_HTTP_SUB_BUFFERED 0x02
#define NGX_HTTP_COPY_BUFFERED 0x04
4. r->limit_rate:对应limit_rate指令,表示request的发送速率限制值。一般地,通过这个值去设置c->write->delayed,当发送速率超过limit时就会设置c->write->delayed,这种情况下就会延迟发送,从而降低request的发送速率。
/*
* avoid the output if there are no last buf, no flush point,
* there are the incoming bufs and the size of all bufs
* is smaller than "postpone_output" directive
*/
/* 在不是最后一个buf,并且不需要flush的情况下,postpone_output指令可以设置当响应内容小于某个值时,延时输出 */
if (!last && !flush && in && size < (off_t) clcf->postpone_output) {
return NGX_OK;
}
/* 请求被delay,设置buffered标记,直接返回 */
if (c->write->delayed) {
c->buffered |= NGX_HTTP_WRITE_BUFFERED;
return NGX_AGAIN;
}
/* 输出内容大小为0,并且没有设置buffered标记,进入清理工作 */
if (size == 0 && !(c->buffered & NGX_LOWLEVEL_BUFFERED)) {
/* 如果是最后一个标记,清空buffered标记 */
if (last) {
r->out = NULL;
c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
return NGX_OK;
}
/* 如果需要flush */
if (flush) {
/* 这块不清楚干什么用的 */
do {
r->out = r->out->next;
} while (r->out);
/* 清空buffered标记 */
c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
return NGX_OK;
}
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
"the http output chain is empty");
ngx_debug_point();
return NGX_ERROR;
}
/* 设置了limit rate */
if (r->limit_rate) {
limit = r->limit_rate * (ngx_time() - r->start_sec + 1)
- (c->sent - clcf->limit_rate_after);
/* 超出发送速率限制 */
if (limit <= 0) {
/* 设置delayed标记,延迟发送请求 */
c->write->delayed = 1;
ngx_add_timer(c->write,
(ngx_msec_t) (- limit * 1000 / r->limit_rate + 1));
/* 设置buffered标记 */
c->buffered |= NGX_HTTP_WRITE_BUFFERED;
return NGX_AGAIN;
}
} else if (clcf->sendfile_max_chunk) {
/* sendfile用的的limit */
limit = clcf->sendfile_max_chunk;
} else {
limit = 0;
}
这段代码根据上述标记进行一些处理,具体看注释。接下来就是发送数据的过程以及后续处理。
sent = c->sent;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http write filter limit %O", limit);
/* send_chain返回的是没有发完的chain */
chain = c->send_chain(c, r->out, limit);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http write filter %p", chain);
if (chain == NGX_CHAIN_ERROR) {
c->error = 1;
return NGX_ERROR;
}
/* 对limit_rate处理,可能会设置delayed标记 */
if (r->limit_rate) {
nsent = c->sent;
if (clcf->limit_rate_after) {
sent -= clcf->limit_rate_after;
if (sent < 0) {
sent = 0;
}
nsent -= clcf->limit_rate_after;
if (nsent < 0) {
nsent = 0;
}
}
delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate + 1);
if (delay > 0) {
c->write->delayed = 1;
ngx_add_timer(c->write, delay);
}
} else if (c->write->ready
&& clcf->sendfile_max_chunk
&& (size_t) (c->sent - sent)
>= clcf->sendfile_max_chunk - 2 * ngx_pagesize)
{
c->write->delayed = 1;
ngx_add_timer(c->write, 1);
}
/* 释放已经发送的chain的内存 */
for (cl = r->out; cl && cl != chain; /* void */) {
ln = cl;
cl = cl->next;
ngx_free_chain(r->pool, ln);
}
/* 重新赋值尚未发送的chain */
r->out = chain;
/* 如果chain不为空,那么buf没有发送完,需要设置buffered标记,并返回NGX_AGAIN */
if (chain) {
c->buffered |= NGX_HTTP_WRITE_BUFFERED;
return NGX_AGAIN;
}
/* 如果已经没有未发送的chain,就清空buffered标记 */
c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
/* 如果其他filter模块buffer了chain并且postponed为NULL,那么返回NGX_AGAIN,需要继续处理buf */
if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {
return NGX_AGAIN;
}
return NGX_OK;
上面就是ngx_http_write_filter的处理过程,ngx_http_header_filter与之类似,只是它处理的是响应头,然后在最后也是通过ngx_http_write_filter输出。