一般HTTP通信时,会使用Content-Length头信息性来通知用户代理(通常意义上是浏览器)服务器发送的文档内容长度,该头信息定义于HTTP1.0协议RFC  1945  10.4章节中。浏览器接收到此头信息后,接受完Content-Length中定义的长度字节后开始解析页面,但如果服务端有部分数据延迟发送吗,则会出现浏览器白屏,造成比较糟糕的用户体验。

解决方案是在HTTP1.1协议中,RFC  2616中14.41章节中定义的Transfer-Encoding: chunked的头信息,chunked编码定义在3.6.1中,所有HTTP1.1 应用都支持此使用trunked编码动态的提供body内容的长度的方式。进行Chunked编码传输的HTTP数据要在消息头部设置:Transfer-Encoding: chunked表示Content Body将用chunked编码传输内容。根据定义,浏览器不需要等到内容字节全部下载完成,只要接收到一个chunked块就可解析页面.并且可以下载html中定义的页面内容,包括js,css,image等。

采用chunked编码有两种选择,一种是设定Server的IO buffer长度让Server自动flush buffer中的内容,另一种是手动调用IO中的flush函数。不同的语言IO中都有flush功能:

l         PHP:    ob_flush(); flush();

l         perl:   STDOUT->autoflush(1);

l         Java:  out.flush();

l         Python:  sys.stdout.flush()

l         ruby:  stdout.flush

采用HTTP1.1的Transfer-Encoding:chunked,并且把IO的buffer flush下来,以便浏览器更早的下载页面配套资源。当不能预先确定报文体的长度时,不可能在头中包含Content-Length域来指明报文体长度,此时就需要通过Transfer-Encoding域来确定报文体长度。

Chunked编码一般使用若干个chunk串连而成,最后由一个标明长度为0的chunk标示结束。每个chunk分为头部正文两部分,头部内容指定下一段正文的字符总数(非零开头的十六进制的数字)和数量单位(一般不写,表示字节).正文部分就是指定长度的实际内容,两部分之间用回车换行(CRLF)隔开。在最后一个长度为0的chunk中的内容是称为footer的内容,是一些附加的Header信息(通常可以直接忽略)。

上述解释过于官方,简而言之,chunked编码的基本方法是将大块数据分解成多块小数据,每块都可以自指定长度,其具体格式如下(BNF文法):

Chunked-Body   = *chunk            //0至多个chunk

last-chunk         //最后一个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)

解释:

l         Chunked-Body表示经过chunked编码后的报文体。报文体可以分为chunk, last-chunk,trailer和结束符四部分。chunk的数量在报文体中最少可以为0,无上限;

l         每个chunk的长度是自指定的,即,起始的数据必然是16进制数字的字符串,代表后面chunk-data的长度(字节数)。这个16进制的字符串第一个字符如果是“0”,则表示chunk-size为0,该chunk为last-chunk,无chunk-data部分。

l         可选的chunk-extension由通信双方自行确定,如果接收者不理解它的意义,可以忽略。

l         trailer是附加的在尾部的额外头域,通常包含一些元数据(metadata, meta means "about information"),这些头域可以在解码后附加在现有头域之后

下面分析用ethereal抓包使用Firefox与某网站通信的结果(从头域结束符后开始):

Address  0..........................  f

000c0                       31

000d0    66 66 63 0d 0a ...............   // ASCII码:1ffc/r/n, chunk-data数据起始地址为000d5

显然,“1ffc”为第一个chunk的chunk-size,转换为int为8188。由于1ffc后,马上就是CRLF,因此没有chunk-extension。chunk-data的起始地址为000d5, 计算可知下一块chunk的起始

地址为000d5+1ffc + 2=020d3,如下:

020d0    .. 0d 0a 31 66 66 63 0d 0a .... // ASCII码:/r/n1ffc/r/n

前一个0d0a是上一个chunk的结束标记符,后一个0d0a则是chunk-size和chunk-data的分隔符。

此块chunk的长度同样为8188, 依次类推,直到最后一块

100e0                          0d 0a 31

100f0    65 61 39 0d 0a......            //ASII码:/r/n/1ea9/r/n

此块长度为0x1ea9 = 7849, 下一块起始为100f5 + 1ea9 + 2 = 11fa0,如下:

11fa0    30 0d 0a 0d 0a                  //ASCII码:0/r/n/r/n

“0”说明当前chunk为last-chunk, 第一个0d 0a为chunk结束符。第二个0d0a说明没有trailer部分,整个Chunk-body结束。

解码流程:

对chunked编码进行解码的目的是将分块的chunk-data整合恢复成一块作为报文体,同时记录此块体的长度。

RFC2616中附带的解码流程如下:(伪代码)

length := 0         //长度计数器置0

read chunk-size, chunk-extension (if any) and CRLF      //读取chunk-size, chunk-extension和CRLF

while(chunk-size > 0 )

{            //表明不是last-chunk

read chunk-data and CRLF            //读chunk-size大小的chunk-data,skip CRLF

append chunk-data to entity-body     //将此块chunk-data追加到entity-body后

length := length + chunk-size

read chunk-size and CRLF          //读取新chunk的chunk-size 和 CRLF

}

read entity-header      //entity-header的格式为name:valueCRLF,如果为空即只有CRLF

while (entity-header not empty)   //即,不是只有CRLF的空行

{

append entity-header to existing header fields

read entity-header

}

Content-Length:=length      //将整个解码流程结束后计算得到的新报文体length,作为Content-Length域的值写入报文中

Remove "chunked" from Transfer-Encoding  //同时从Transfer-Encoding中域值去除chunked这个标记

length最后的值实际为所有chunk的chunk-size之和,在上面的抓包实例中,一共有八块chunk-size为0x1ffc(8188)的chunk,剩下一块为0x1ea9(7849),加起来一共73353字节。
      注:对于上面例子中前几个chunk的大小都是8188,可能是因为:"1ffc" 4字节,""r"n"2字节,加上块尾一个""r"n"2字节一共8字节,因此一个chunk整体为8196,正好可能是发送端一次TCP发送的缓存大小。

最后提供一段PHP版本的chunked解码代码:

$chunk_size = (integer)hexdec(fgets( $socket_fd, 4096 ) );

while(!feof($socket_fd) && $chunk_size > 0)

{

$bodyContent .= fread( $socket_fd, $chunk_size );

fread( $socket_fd, 2 ); // skip /r/n
    $chunk_size = (integer)hexdec(fgets( $socket_fd, 4096 ) );

}

HTTP1.1中CHUNKED编码解析的更多相关文章

  1. HTTP1.1中CHUNKED编码解析(转载)

    HTTP1.1中CHUNKED编码解析 一般HTTP通信时,会使用Content-Length头信息性来通知用户代理(通常意义上是浏览器)服务器发送的文档内容长度,该头信息定义于HTTP1.0协议RF ...

  2. HttpWebRequest请求http1.1的chunked的解析问题记录

    问题:我的请求获取不到URL对应的内容(换个浏览器可以). 第一步对比wirshark截包看HTTP请求头,发现我这缺失一部分请求头. 对着官方文档添加即可.https://msdn.microsof ...

  3. HTTP协议中的chunked编码解析

    \r\n\r\n"了. chunked编码很简单,是不是? 不过,在协议还原的实现过程中,如何高效高性能地对chunked进行解码,是一件值得挑战的事哦.毕竟,HTTP的流量占比不小的. 长 ...

  4. Java Web中的编码解析

    在springmvc工程web.xml中配置中文编码 <!-- 配置请求过滤器,编码格式设为UTF-8,避免中文乱码--> <filter> <filter-name&g ...

  5. Google Directions API 中路线编码解析

    public List<Location> GetGeoPoints(string encoded) { List<Location> poly = new List<L ...

  6. http协议中content-length 以及chunked编码分析

    转载请注明出处 http://blog.csdn.net/yankai0219/article/details/8269922 0.序 1.http/1.1协议中与chunked编码的相关字段 1)E ...

  7. 「Python 编程」编码实现网络请求库中的 URL 解析器

    摘要:怎么写出更短的代码并不是这次要讨论的话题.今天我们来研究一下:运行代码的计算机是如何找到目标服务器的? 相信各位 Python 开发者都用过 Requests 库,有些朋友还用过 WebSock ...

  8. python语言中的编码问题

    在编程的过程当中,常常会遇到莫名其妙的乱码问题.很多人选择出了问题直接在网上找答案,把别人的例子照搬过来,这是快速解决问题的一个好办法.然而,作为一个严谨求实的开发者,如果不从源头上彻底理解乱码产生的 ...

  9. java中采用dom4j解析xml文件

    一.前言 在最近的开发中用到了dom4j来解析xml文件,以前听说过来解析xml文件的几种标准方式:但是从来的没有应用过来,所以可以在google中搜索dmo4j解析xml文件的方式,学习一下dom4 ...

随机推荐

  1. vivado与modelsim的联合仿真(一)

    vivado软件中也自带仿真工具,但用了几天之后感觉仿真速度有点慢,至少比modelsim慢挺多的.而modelsim是我比较熟悉的一款仿真软件,固然选它作为设计功能的验证.为了将vivado和mod ...

  2. 【Java集合源代码剖析】Java集合框架

    转载轻注明出处:http://blog.csdn.net/ns_code/article/details/35564663 Java集合工具包位于Java.util包下,包括了非常多经常使用的数据结构 ...

  3. 设计模式之里氏替换原则(LSP)

    在java等面向对象编程语言里面,我想继承性应该是一大特色吧!所以今天所要讲解的里氏替换原则主要是针对这一特性而提出来的,当我们定义对象的时候,尽量找出对象之间的相同点,然后将其抽象成基类对象.比如水 ...

  4. 【转载】浏览器加载和渲染html的顺序

    1.浏览器加载和渲染html的顺序 1.IE下载的顺序是从上到下,渲染的顺序也是从上到下,下载和渲染是同时进行的.2.在渲染到页面的某一部分时,其上面的所有部分都已经下载完成(并不是说所有相关联的元素 ...

  5. codeblocks如何watch数组

    codeblocks13.12+GDB 调试的时候,main传了一个int a[10]给quicksort 但是在quicksort内部,debugger把a看成一个pointer而不是array,所 ...

  6. iOS valueForKeyPath快速计算求和、平均值、最大、最小

    iOS中开始取出数组中最大值,最小值除了使用排序的方式,还可以使用valueForKeyPath的方式直接取出 array = @[@(10),@(100),@(20),@(97)]; CGFloat ...

  7. MFC 控件RadioButton和CheckBox区别

    1. 单个RadioButton在选中后,通过点击无法变为未选中 单个CheckBox在选中后,通过点击可以变为未选中 2. 一组RadioButton,只能同时选中一个 一组CheckBox,能同时 ...

  8. 简单介绍一下vue2.0

    Vue Vue是用于构建用户界面的渐进框架.作者尤雨熙特别强调它与其他的框架不同,Vue是渐进式的框架,可以逐步采用,不必一下就通过框架去重构项目. 另外Vue的核心库只专注于视图层,这样就更容易与其 ...

  9. Java基础01 从HelloWorld到面向对象(转载)

    Java是完全面向对象的语言.Java通过虚拟机的运行机制,实现“跨平台”的理念. "Hello World!" public class HelloWorld{    publi ...

  10. redis的下载

    网址一:https://github.com/dmajkic/redis/downloads 网址二:http://windows.php.net/downloads/pecl/releases/re ...