概述
这篇博客中我们重点阐述nginx http proxy模块如何向客户端发送响应。这也是一个比较复杂的过程,我们在叙述的过程中省略去一些过滤模块的钩子函数,直接进入发送响应数据的处理逻辑,http proxy模块触发发送响应的函数是ngx_event_pipe_write_to_downstream,这篇博客就对这个函数作一些简单分析。
实现
总的说来,ngx_event_pipe_write_to_downstream实现中主要有三个过程:1.准备写出的缓冲区;2. 将缓冲区发送给客户端;3.更新本地缓存状态。我们分别从这三点来分析一次完整的数据发送过程。
- 准备写出缓冲区
之前的博客中我们说到了nginx会将不同状态的缓冲区链入不同链表上。与发送相关的缓冲区链表有三:busy链表,out链表,in链表。后面的两个链表我们前面已经做过分析,busy链表存储的是上次发送过程中没有写出的部分。
因此,我们发送的顺序应该是:busy链表,out链表,in链表(必须要按照响应body的偏移顺序发送)。另外,每次发送多少个缓冲区也是可以设置的,nginx可以通过proxy_busy_bufs来设置每次要发送的响应大小。因此,整个的准备缓冲区代码如下所示:
for (cl = p->busy; cl; cl = cl->next) {
if (cl->buf->recycled) {
// ʲôÇé¿öÏ»á³öÏÖǰһ¸ö»º³åÇøºÍºóÒ»¸ö»º³åÇø
// µÄÆðʼµØÖ·Ïàͬ?
if (prev == cl->buf->start) {
continue;
}
bsize += cl->buf->end - cl->buf->start;
prev = cl->buf->start;
}
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
"pipe write busy: %uz", bsize);
out = NULL;
// 如果busy buffers链表上的缓冲区数量已经足够,那么无需再从out和in链表上去取了,直接flush
if (bsize >= (size_t) p->busy_size) {
flush = 1;
goto flush;
}
flush = 0;
ll = NULL;
prev_last_shadow = 1;
for ( ;; ) {
if (p->out) { // 优先发送out链表,因为这部分响应偏移在前
cl = p->out;
if (cl->buf->recycled) {
ngx_log_error(NGX_LOG_ALERT, p->log, 0,
"recycled buffer in pipe out chain");
}
p->out = p->out->next;
} else if (!p->cacheable && p->in) {
cl = p->in;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, p->log, 0,
"pipe write buf ls:%d %p %z",
cl->buf->last_shadow,
cl->buf->pos,
cl->buf->last - cl->buf->pos);
if (cl->buf->recycled && prev_last_shadow) {
if (bsize + cl->buf->end - cl->buf->start > p->busy_size) {
flush = 1;
break;
}
bsize += cl->buf->end - cl->buf->start;
}
prev_last_shadow = cl->buf->last_shadow;
p->in = p->in->next;
} else {
break;
}
cl->next = NULL;
if (out) {
*ll = cl;
} else {
out = cl;
}
ll = &cl->next;
}
- 发送响应
发送响应的过程略显简单,直接调用了如下函数
// 最终调用ngx_http_write_filter()将数据发送出去
rc = p->output_filter(p->output_ctx, out);
只是我们需要了解的一点是,这个发送过程可能并没有将所有的准备好的响应数据发送出去,可能只发送了部分而已,另外发送过程中可能由于种种原因出错,在发送函数中会设置种种的错误码。
- 更新本地缓存状态
发送完成后需要更新本地缓存状态。如busy buffer链表上的缓冲区可以释放了、本次未发送成功的buffer需要添加到busy buffer链表上 ......,这个过程会在下面的代码中实现:
// 将已经发送出去的缓冲区释放,添加到free链表
// 将尚未发送出去的缓冲区添加至busy链表,下次优先发送
ngx_chain_update_chains(p->pool, &p->free, &p->busy, &out, p->tag);
另外,对于发送出错,还得设置错误码:
if (rc == NGX_ERROR) {
p->downstream_error = 1;
return ngx_event_pipe_drain_chains(p);
}
由于上面刚刚将发送完成的缓冲区添加到了free链表,注意:这里只是将管理缓冲区的数据结构释放到free链表,真正的原始缓冲区可能尚未释放(插入到free_raw_bufs链表)。因此,接下来我们得处理这种情况,需要遍历free链表上的所有buffer,如果其原始的数据缓冲区尚未释放,那么free it。
for (cl = p->free; cl; cl = cl->next) {
if (cl->buf->temp_file) {
if (p->cacheable || !p->cyclic_temp_file) {
continue;
}
/* reset p->temp_offset if all bufs had been sent */
if (cl->buf->file_last == p->temp_file->offset) {
p->temp_file->offset = 0;
}
}
/* TODO: free buf if p->free_bufs && upstream done */
/* add the free shadow raw buf to p->free_raw_bufs */
if (cl->buf->last_shadow) {
if (ngx_event_pipe_add_free_buf(p, cl->buf->shadow) != NGX_OK) {
return NGX_ABORT;
}
cl->buf->last_shadow = 0;
}
cl->buf->shadow = NULL;
}
}