Tag Archives: http

理解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”分块的方式传输给客户端。

命令行测试ssl回话

工作到现在,经常在和HTTP协议打交道,所以HTTP相关的开发工具和调试工具是必备的;平时工作中调试bug的时候,一般都是调试单个回话的居多,所以经常使用telnet调试HTTP会话,telnet虽然功能极其简单,但是也极其的强大,因为你可以构造任何http header给服务器,这样你就可以研究各种条件下http服务器的返回内容;下面的一个例子。

但是telnet不支持ssl,需要这样测试https,怎么办,用openssl的s_client客户端就可以了,前提是你安装openssl:

openssl s_client -connect www.example.com:443

如下图:

 

参考资料:

1.http://www.bearfruit.org/2008/04/17/telnet-for-testing-ssl-https-websites/

谷歌搜索关键字telnet ssl,这是第一篇文章,也是从这里学到的;

2.http://www.ibm.com/developerworks/cn/linux/l-cn-sclient/index.html

这篇来自ibm的文章列举了openssl的s_client客户端的几个高级用法;

3.http://www.blogjava.net/ycyk168/archive/2009/11/27/303946.html

这篇博文举了几个s_client应用的简单例子;

4.http://www.openssl.org/docs/apps/s_client.html

这是openssl s_client客户端的官方手册。

http请求中的via头可能会影响iis6 web服务器的压缩行为

哎,终于更新博客了,这是在大猫那边买空间之后为数不多的文章之一;工作之后发现时间真的是越来越紧张,不像在学校那样时间宽裕,又想念学校生活了。。几个月之前就想更新,学习、加班什么乱七八糟的事耽误着,总让人有种心有余力不足的感觉,差不多空间又要续费了,再不更新资源严重浪费了。

今天记录一下工作中修bug中遇到的一个技术问题,主要是http的via头会影响iis6 web服务器的默认压缩行为;更具体一点,就是如果请求中有via头,并且希望得到压缩内容,也就是”Accept-Encoding”头中的含有有效压缩字符,如”Accept-Encoding: gzip”,那么iis6服务器默认情况下是不回复压缩的内容;因为iis6服务器认为,含有via头的http请求来自代理服务器,它们不能很好的处理有编码的内容;确实是这样,最近修了公司产品几个关于压缩的bug。

我们举例子来理解它的行为:

1.环境:iis6服务器,我用windows 2003装的iis6;任何能发送含有via头请求的浏览器,我用telnet,非常简单但是非常强大;如下图所示。

有via,没压缩内容

没via头,有压缩内容

 

2.分别发送含有via头的请求和没有via头的请求,我们发现,来自iis6服务器的内容不一样。原因上面已经提及。那么,如何解决?简单,在请求中屏蔽via头,还有一个办法,调整iis6的一个参数,就能让它回复含有编码的内容给含有via头的请求:在配置文件中,C:\WINDOWS\system32\inetsrv\MetaBase.xml,把变量HcNoCompressionForProxies的值变为FALSE;这样,就能收到压缩编码了。如下图:

有via,有压缩