Tag Archives: chunked

理解http中的chunk编码

概述

http的chunk编码(分块传输编码)是HTTP/1.1(RFC2616)中新型的传输编码,是为了解决服务器动态产生的内容的传输问题. 一般情况下,HTTP的response是整体发送给客户端的,头中的Content-Length表示这个消息体的长度.数据的长度很重要,因为这直接决定了客户端如何正确解析返回的http response(应答). http的chunk编码就是把这一个整体的response分成多块数据进行传输,每块数据都有数据长度和具体的数据,最后一个数据块是0;具体的格式下面解释.

符号和名词定义

这里使用扩展的巴科斯范式(Augmented BNF)来描述下面的一些正式用语和语法.

chunk:分块;
CR = <US-ASCII CR, carriage return (13)>,ASCII表中的归位键;
LF = <US-ASCII LF, linefeed (10)>,ASCII表中的换行键;
CRLF = CR LF,一般是"\r\n";
HEX  = "A" | "B" | "C" | "D" | "E" | "F"
			| "a" | "b" | "c" | "d" | "e" | "f" | DIGIT |
OCTET = <any 8-bit sequence of data>

*rule,零条或者多条rule;完全的表达式"<n>*<m>rule",最少n条,之多m条rule,例如"*(rule)",代表任意数目的rule,包括0;"1*(rule)"代表至少有1条rule;"1*2rule",允许1条或者2条rule.

[rule],代表此条rule是可选的,例如"[foo bar]",等价于"*1(foo bar)";
"literal",字符串"literal",除非特别声明,要不然默认大小写敏感;

rule1 | rule2,或者的关系,rule1或者rule2.

英文版:

CR             = <US-ASCII CR, carriage return (13)>
LF             = <US-ASCII LF, linefeed (10)>
CRLF           = CR LF
HEX            = "A" | "B" | "C" | "D" | "E" | "F"
                      | "a" | "b" | "c" | "d" | "e" | "f" | DIGIT
OCTET          = <any 8-bit sequence of data>

*rule
      The character "*" preceding an element indicates repetition. The
      full form is "<n>*<m>element" indicating at least <n> and at most
      <m> occurrences of element. Default values are 0 and infinity so
      that "*(element)" allows any number, including zero; "1*element"
      requires at least one; and "1*2element" allows one or two.

[rule]
      Square brackets enclose optional elements; "[foo bar]" is
      equivalent to "*1(foo bar)".

rule1 | rule2
      Elements separated by a bar ("|") are alternatives, e.g., "yes |
      no" will accept yes or no.

更多请看 http://tools.ietf.org/html/rfc2616#section-2.2

格式

非正式格式:

chunk消息体 = (零个或者多个chunk) + 最后一个chunk + trailer + CRLF

正式格式:

Chunked-Body   = *chunk
                 last-chunk
                 trailer
                 CRLF

chunk          = chunk-size [ chunk-extension ] CRLF
                 chunk-data CRLF
chunk-size     = 1*HEX
last-chunk     = 1*("0") [ chunk-extension ] CRLF

chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
chunk-ext-name = token
chunk-ext-val  = token | quoted-string
chunk-data     = chunk-size(OCTET)
trailer        = *(entity-header CRLF)

解释:一个chunk编码块是由0个或者多个chunk块组成,每个chunk块有长度和数据,并且结束块是0,最后加一个CRLF;trailer很少见,可以不管.

例子

数据包中的数据:

4\r\n
Wiki\r\n
5\r\n
pedia\r\n
E\r\n
 in\r\n
\r\n
chunks.\r\n
0\r\n
\r\n

解释:第一个数据块长度为4,数据内容为”Wiki”;第二个数据块长度为5,数据内容为”pedia”;第三个数据块长度为E(十六进制,十进制是14),数据内容为” in\r\n\r\nchunks.”;”0\r\n”是最后一个数据块.

浏览器显示的数据:

Wikipedia in

chunks.

一个发chunk编码的简单http server

code

代码贴在github gist: https://gist.github.com/inix/d682e4e72cb6aea16132

use IO::Socket::INET;
$| = 1;

#response头部
my $response_hdr = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nServer: perl tcp server\r\nTransfer-Encoding: chunked\r\nConnection: Keep-Alive\r\n\r\n";

#创建socket,并且监听8888端口
$socket = new IO::Socket::INET (
LocalHost => '0.0.0.0',
LocalPort => '8888',
Proto => 'tcp',
Listen => 5,
Reuse => 1
) or die "ERROR in Socket Creation : $!\n";

while(1)
{
    #循环等待client发完请求
    $client_socket = $socket->accept();
    $request = "";
    while(defined($bf = <$client_socket>)) {
        $request.=$bf;

        #两个\r\n代表请求头发完,详细请阅读rfc2616
        if ($request =~ /\r\n\r\n/ and $request =~ /GET/) {
            print "we got request \"$request\"\n";
            last;
        }
    }

    #开始返回response给client
    print $client_socket "$response_hdr"; #打印头
    for(my $i = 0;$i < 5;$i ++){ #打印5个数据块,每个数据块长度是6,数据块内容为"1</br>"
        print $client_socket "6\r\n1</br>\r\n";
    }
    print $client_socket "0\r\n\r\n"; #打印最后的一个数据块

    # notify client that response has been sent
    shutdown($client_socket, 1);
}

$socket->close();

运行代码:

perl ./chunk-tcp-server.pl

访问:

用telnet:

http-chunk-telnet-req.png

用浏览器: http-chunk-ie-req.png 请注意两者的 不同点

FAQ

1.http的chunk传输编码只有HTTP/1.1支持吗?

是的.只有HTTP 1.1支持这种编码,HTTP 1.0不支持这种传输编码,而且RFC2616说,HTTP 1.1的程序必须支持这种编码,详见:http://tools.ietf.org/html/rfc2616#section-3.6.1

2.如果HTTP 1.0中出现”Transfer-Encoding: chunked”,会发生什么情况?

出现编码错误.有两种情况:request和response其中之一为1.0,request和response都是1.0. 编码错误,如果是明文,我们能从浏览器看到chunk的长度大小。稍微修改一下我们的程序,把$response\_Hdr中的”Http/1.1″变为”HTTP/1.0″,用IE访问,如下图所示: http-chunk-ie-resp10.png 可以看到chunk的长度都被显示出来了。 如果非明文,那么浏览器有可能无法显示内容,并且报告内容编码错误。

3.”Transfer-Encoding”和”Content-Encoding”有什么区别?

“Transfer-Encoding”是传输属性,即以什么方式传输,一般就是”Transfer-Encoding: chunked”,response的内容可以是明文,也可以是压缩的内容;而”Content-Encoding”是response消息以什么方式编码送给客户端,通常用于说明response的压缩编码方式,比如”Content-Encoding: gzip”或者”Content-Encoding: deflate”,告诉客户端response是用gzip和deflate格式压缩的,而经过压缩后,他们可以用”Content-Length”固定长度的方式传输,也可以用”Transfer-Encoding: chunked”分块的方式传输给客户端。