打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
nginx中处理http header详解(2)
userphoto

2012.02.19

关注

nginx中处理http header详解(2)

Hello there! If you are new here, you might want to subscribe to the RSS feed for updates on this topic.

原创文章,转载请注明: 转载自pagefault

本文链接地址: nginx中处理http header详解(2)

然后是charset filter,这个主要是处理nginx内部的charset命令,转换为设置的编码。这个filter就不介绍了,主要是一个解码的过程。

再接下来是chunk filter,它主要是生成chunk数据,这里要注意nginx只支持服务端生成chunk,而不支持客户端发送的chunk数据。chunk的格式很简单,简单的来说就是大小+数据内容。
先来看chunk的header filter,在filter中,主要是用来判断是否需要chunk数据,然后设置相关标记位,以便于后面的body filter处理.

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
static ngx_int_t
ngx_http_chunked_header_filter(ngx_http_request_t *r)
{
    ngx_http_core_loc_conf_t  *clcf;
//如果是304,204或者是head方法,则直接跳过chunk filter。
    if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED
        || r->headers_out.status == NGX_HTTP_NO_CONTENT
        || r != r->main
        || (r->method & NGX_HTTP_HEAD))
    {
        return ngx_http_next_header_filter(r);
    }
 
//如果content_length_n为-1 则进入chunk处理,下面我们会看到这个值在那里设置,也就是nginx中如何打开chunk编码.
    if (r->headers_out.content_length_n == -1) {
        if (r->http_version < NGX_HTTP_VERSION_11) {
            r->keepalive = 0;
 
        } else {
            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
//chunked_transfer_encoding命令对应的参数,这个值默认是1,因此默认chunk是打开的。
            if (clcf->chunked_transfer_encoding) {
 
                r->chunked = 1;
 
            } else {
                r->keepalive = 0;
            }
        }
    }
 
    return ngx_http_next_header_filter(r);
}

然后来看content_length_n何时被改变为-1,也就是准备chunk编码,这个值是在ngx_http_clear_content_length中被改变了。也就是如果希望chunk编码的话,必须调用这个函数。

1
2
3
4
5
6
7
8
#define ngx_http_clear_content_length(r)                                      \
                                                                              \
//设置长度为-1.
    r->headers_out.content_length_n = -1;                                     \
    if (r->headers_out.content_length) {                                      \
        r->headers_out.content_length->hash = 0;                              \
        r->headers_out.content_length = NULL;                                 \
    }

然后来看body filter是如何处理的。这里的处理其实很简单,只不过特殊处理下last buf.大体流程是这样子的,首先计算chunk的大小,然后讲将要发送的buf串联起来,然后将大小插入到数据buf之前,最后设置tail buf,如果是last buf,则结尾是

1
CRLF "0" CRLF CRLF

如果不是last buf,则结尾就是一个CRLF,这些都是严格遵守rfc2616。
来看详细的代码:

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
44
    if (size) {
        b = ngx_calloc_buf(r->pool);
        if (b == NULL) {
            return NGX_ERROR;
        }
 
        /* the "0000000000000000" is 64-bit hexadimal string */
//分配大小
        chunk = ngx_palloc(r->pool, sizeof("0000000000000000" CRLF) - 1);
        if (chunk == NULL) {
            return NGX_ERROR;
        }
 
        b->temporary = 1;
        b->pos = chunk;
//设置chunk第一行的数据,也就是大小+回车换行
        b->last = ngx_sprintf(chunk, "%xO" CRLF, size);
 
        out.buf = b;
    }
//如果是最后一个buf。
    if (cl->buf->last_buf) {
        b = ngx_calloc_buf(r->pool);
        if (b == NULL) {
            return NGX_ERROR;
        }
 
        b->memory = 1;
        b->last_buf = 1;
//设置结尾
        b->pos = (u_char *) CRLF "0" CRLF CRLF;
 ..........................................................
 
    } else {
........................................................
        b->memory = 1;
//否则则是中间的chunk块,因此结尾为回车换行。
        b->pos = (u_char *) CRLF;
        b->last = b->pos + 2;
    }
 
    tail.buf = b;
    tail.next = NULL;
    *ll = &tail;

然后是gzip filter,它主要是处理gzip的压缩.其中在header filter中,判断accept-encoding头,来看客户端是否支持gzip压缩,然后设置Content-Encoding为gzip,以便与client解析。然后核心的处理都在body filter里面。

先来介绍下filter的主要流程,这里有一个要强调的,那就是nginx里面所有的filter处理基本都是流式的,也就是有多少处理多少。由于是gzip压缩,因此这里会有一个输入,一个输出,因此这里就分为3步,第一步取得输入buf,第二步设置输出buf,第三步结合前两步取得的buf,交给zlib库去压缩,然后输出到前面设置的buf。

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
    for ( ;; ) {
 
        /* cycle while we can write to a client */
 
        for ( ;; ) {
 
            /* cycle while there is data to feed zlib and ... */
//设置正确的输入buf
            rc = ngx_http_gzip_filter_add_data(r, ctx);
 
            if (rc == NGX_DECLINED) {
                break;
            }
 
            if (rc == NGX_AGAIN) {
                continue;
            }
//设置输出buf
            rc = ngx_http_gzip_filter_get_buf(r, ctx);
 
            if (rc == NGX_DECLINED) {
                break;
            }
 
            if (rc == NGX_ERROR) {
                goto failed;
            }
//开始进行压缩
            rc = ngx_http_gzip_filter_deflate(r, ctx);
 
            if (rc == NGX_OK) {
                break;
            }
 
            if (rc == NGX_ERROR) {
                goto failed;
            }
            /* rc == NGX_AGAIN */
        }

这里有一个小细节要注意的,就是在ngx_http_gzip_filter_add_data中,在nginx中会一个chain一个chain进行gzip压缩,压缩完毕后,输入chain也就可以free掉了,可是nginx不是这么做的,他会在当所有的chain都被压缩完毕后再进行free,这是因为gzip压缩对于cpu cache很敏感,而当你free buf的时候,有可能会导致cache trashing,也就是会将一些cache的数据换出去。

1
2
3
4
5
6
7
8
9
10
11
12
    if (ctx->copy_buf) {
//这里注释很详细
        /*
         * to avoid CPU cache trashing we do not free() just quit buf,
         * but postpone free()ing after zlib compressing and data output
         */
 
        ctx->copy_buf->next = ctx->copied;
        ctx->copied = ctx->copy_buf;
//简单的赋为NULL,而不是free掉.
        ctx->copy_buf = NULL;
    }

最终在ngx_http_gzip_filter_free_copy_buf中free所有的gzip压缩的数据。从这里我们能看到nginx对于细节已经抓到什么地步了.

最后一个是header filter,也就是发送前最后一个head filter,这个filter里面设置对应的头以及status_lines,并且根据对应的status code设置对应的变量。所以这个filter是只有head filter的。这里的处理都没什么难的地方,就是简单的设置对应的头,因此就不详细的分析代码。它的流程大体就是先计算size,然后分配空间,最后copy对应的头。

就看一段代码,关于keepalive的,我们知道http1.1 keepalive是默认开启的,而http1.0它是默认关闭的,而nginx的keepalive_timeout命令则只是用来设置keepalive timeout的.对应clcf->keepalive_header。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//是否打开了keepalive,如果是1.1则默认是开启的
    if (r->keepalive) {
//设置Connection的头.
        len += sizeof("Connection: keep-alive" CRLF) - 1;
//不同浏览器行为不一样的。
        /*
         * MSIE and Opera ignore the "Keep-Alive: timeout=<N>" header.
         * MSIE keeps the connection alive for about 60-65 seconds.
         * Opera keeps the connection alive very long.
         * Mozilla keeps the connection alive for N plus about 1-10 seconds.
         * Konqueror keeps the connection alive for about N seconds.
         */
 
        if (clcf->keepalive_header) {
//设置timeout
            len += sizeof("Keep-Alive: timeout=") - 1 + NGX_TIME_T_LEN + 2;
        }
 
    } else {
        len += sizeof("Connection: closed" CRLF) - 1;
    }
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
杂谈Nginx与HTTP协议 ? xlq的博客
隐藏web服务的头信息
ETag使用效果对比及经验分享(附ETag配置教程)(2)
使用Nginx反向代理和proxy
Nginx与Gzip请求 | 火丁笔记 lua zip
Nginx隐藏主机信息,proxy
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服