Just For Coding

Keep learning, keep living …

NGINX发送响应分析

NGINX中使用ngx_http_output_filter()向一个请求发送响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ngx_int_t
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t          rc;
    ngx_connection_t  *c;

    c = r->connection;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http output filter "%V?%V"", &r->uri, &r->args);

    rc = ngx_http_top_body_filter(r, in);

    if (rc == NGX_ERROR) {
        /* NGX_ERROR may be returned by any filter */
        c->error = 1;
    }

    return rc;
}

其中,r是请求结构体,in为以ngx_chain_t结构链接起来的需要发送的内容。ngx_http_output_filter()会调用ngx_http_top_body_filter()。NGINX采用filter机制对响应进行处理。filter分为header filterbody filter。NGINX分别将两种filter各构建成一个链表。全局变量ngx_http_top_header_filter指向header filter链表的头结点,而全局变量ngx_http_top_body_filter指向body filter链表的头结点。每个filter中用一个变量记录下一个filter,依据情况决定是调用下一个filter,还是直接返回。

body filter链表顺序如下图:

token data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  +--------------------------+
  |ngx_http_range_body_filter|
  +----------+---------------+
             |
             v
  +----------+---------+
  |ngx_http_copy_filter|
  +----------+---------+
             |
             v
  +----------+-----------------+
  |ngx_http_charset_body_filter|
  +----------+-----------------+
             |
             v
  +----------+-------------+
  |ngx_http_ssi_body_filter|
  +----------+-------------+
             |
             v
  +----------+-------------+
  |ngx_http_postpone_filter|
  +----------+-------------+
             |
             v
  +----------+--------------+
  |ngx_http_gzip_body_filter|
  +----------+--------------+
             |
             v
  +----------+-----------------+
  |ngx_http_chunked_body_filter|
  +----------+-----------------+
             |
             v
  +---------------------+
  |ngx_http_write_filter|
  +---------------------+

header filter链表顺序如图:

token data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  +----------------------------+
  |ngx_http_not_modified_filter|
  +----------+-----------------+
             |
             v
  +----------+------------+
  |ngx_http_headers_filter|
  +----------+------------+
             |
             v
  +----------+-----------+
  |ngx_http_userid_filter|
  +----------+-----------+
             |
             v
  +----------+-------------------+
  |ngx_http_charset_header_filter|
  +----------+-------------------+
             |
             v
  +----------+---------------+
  |ngx_http_ssi_header_filter|
  +----------+---------------+
             |
             v
  +----------+----------------+
  |ngx_http_gzip_header_filter|
  +----------+----------------+
             |
             v
  +----------+-----------------+
  |ngx_http_range_header_filter|
  +----------+-----------------+
             |
             v
  +----------+-------------------+
  |ngx_http_chunked_header_filter|
  +----------+-------------------+
             |
             v
  +----------+-----------+
  |ngx_http_header_filter|
  +----------------------+

ngx_http_write_filter()是最后一个被调用的body filter,它进行真正的网络I/O操作,将响应发送给客户端。实际上, ngx_http_header_filter()也是调用ngx_http_write_filter()来发送响应中的headers。

ngx_http_write_filter()的简化流程如下:

  • 检查之前是否有错误发生(c->error被置位)。如果有错误发生,则没有必要再进行网络I/O操作,直接返回NGX_ERROR。
1
2
3
if (c->error) {
    return NGX_ERROR;
}
  • 计算之前没有发送完成的内容大小并检查是否存在特殊标志。为了优化性能,当没有必要立即发送响应且响应内容大小没有达到设置的阀值时,NGINX可以暂时推迟发送该部分响应。参看:postpone_output指令。flush标志表示需要立即发送响应。recycled表示该buffer需要循环使用,因而需要立即发送以释放该buffer被重新使用。last标志表示该buffer是响应的最后一部分内容,因而也需要立即发送。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for (cl = r->out; cl; cl = cl->next) {
    ll = &cl->next;

    ......

    size += ngx_buf_size(cl->buf);

    if (cl->buf->flush || cl->buf->recycled) {
        flush = 1;
    }

    if (cl->buf->last_buf) {
        last = 1;
    }
}
  • 计算本次将发送的内容大小,检查是否存在特殊标志,并将内容链接到r->out上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 = cl;
    ll = &cl->next;

    ...

    size += ngx_buf_size(cl->buf);

    if (cl->buf->flush || cl->buf->recycled) {
        flush = 1;
    }

    if (cl->buf->last_buf) {
        last = 1;
    }
}
  • 根据情况决定是需要真正进行网络I/O操作, 还是直接返回。
1
2
3
if (!last && !flush && in && size < (off_t) clcf->postpone_output) {
    return NGX_OK;
}
  • 真正进行网络I/O操作,发送内容。
1
chain = c->send_chain(c, r->out, limit);
  • 回收发送完成内容的buffer和chain结构, 将没有发送完成的内容存入r->out
1
2
3
4
5
6
7
for (cl = r->out; cl && cl != chain; /* void */) {
    ln = cl;
    cl = cl->next;
    ngx_free_chain(r->pool, ln);
}

r->out = chain;
  • 根据发送是否完成,返回NGX_OKNGX_AGAIN
1
2
3
4
5
6
7
8
9
10
11
12
if (chain) {
    c->buffered |= NGX_HTTP_WRITE_BUFFERED;
    return NGX_AGAIN;
}

c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;

if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {
    return NGX_AGAIN;
}

return NGX_OK;

此外,ngx_http_write_filter()中也处理了限速发送的逻辑,本文不详述。