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

HTTP1.1中CHUNKED编码解析

一般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 ) );

}

 

 

 

其C语言的解码如下,java思路相同

int nBytes;

char* pStart = a;    // a中存放待解码的数据

char* pTemp;

char strlength[10];   //一个chunk块的长度

chunk  : pTemp =strstr(pStart,"/r/n");

             if(NULL==pTemp)

             {

                      free(a);

                 a=NULL;

                     fclose(fp);

                     return -1;

             }

             length=pTemp-pStart;

             COPY_STRING(strlength,pStart,length);

             pStart=pTemp+2;

             nBytes=Hex2Int(strlength); //得到一个块的长度,并转化为十进制

                              

             if(nBytes==0)//如果长度为0表明为最后一个chunk

            {

                free(a);

                       fclose(fp);

                       return 0;

               }

               fwrite(pStart,sizeof(char),nBytes,fp);//将nBytes长度的数据写入文件中

               pStart=pStart+nBytes+2; //跳过一个块的数据以及数据之后两个字节的结束符

               fflush(fp);

               goto chunk; //goto到chunk继续处理

  

 

如何将一个十进制数转化为十六进制

char *buf = (char *)malloc(100);

char *d = buf;

int shift = 0;

unsigned long copy = 123445677;

while (copy) {

         copy >>= 4;

         shift++;

}//首先计算转化为十六进制后的位数

if (shift == 0)

         shift++;

shift <<= 2; //将位数乘于4,如果有两位的话 shift为8

while (shift > 0) {

         shift -= 4;

         *(buf) = hex_chars[(123445677 >> shift) & 0x0F];

          buf++;

}

*buf = '/0';

  原文链接:http://blog.csdn.net/zhangboyj/article/details/6236780

  参考:http://bbs.csdn.net/topics/390333793

转载于:https://www.cnblogs.com/12taotie21/p/3916980.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/407359.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解析可变参数函数的实现原理(printf,scanf)

From: http://hi.baidu.com/huifeng00/blog/item/085e8bd198f46ed3a8ec9a0b.html 学习C的语言的时候&#xff0c;肯定接触到标准输出和标准输入函数。 这个函数给人的感觉很强大&#xff0c;因为它很另类&#xff0c;就是这个函数的参数是可变的。 下面是一个自己编写的可变…

花生葫芦球 健身新运动

国民健康天后张淳淳老师率先再将风靡欧美的“花生葫芦球(FITNESS BALL)”&#xff0c;推广给日、港、台的朋友&#xff0c;同时结合国内外体适能教练与专家&#xff0c;研发出一套减压、塑身运动课程&#xff0c;引领全民健康塑身运动。 花生葫芦球 健身新运动美大腿后健肌群伸…

JS之数组元素排序方法sort

作用&#xff1a;sort() 方法用于对数组的元素进行排序 语法&#xff1a;arrayObject.sort(sortby) 参数&#xff1a;可选。规定排序顺序。必须是函数 返回值&#xff1a;对数组的引用。请注意&#xff0c;数组在原数组上进行排序&#xff0c;不生成副本 注意1&#xff1a;…

js中自己实现bind函数的方式

前言 最近由于工作比较忙&#xff0c;好久都没时间静下心来研究一些东西了。今天在研究 call 和 apply 的区别的时候&#xff0c;看到 github 上面的一篇文章&#xff0c;看完以后&#xff0c;感觉启发很大。 文章链接为 https://github.com/lin-xin/blog/issues/7 &#xff…

我的C语言可变参数的实现

实现环境&#xff1a;Fedora12 gcc 任务&#xff1a;用C语言实现一个参数可变的函数&#xff0c;以方便输出。 源代码如下&#xff1a; #include <stdio.h>#include <stdarg.h>#include <string.h>int sum(int data, ...){int i data, s 0;va_list vl;…

Leetcode刷题(1)两数之和

最好的种树是十年前,其次是现在。歌谣 每天一个前端小知识 提醒你改好好学习了 知乎博主 csdn博主 b站博主 放弃很容易但是坚持一定很酷 我是歌谣 喜欢就一键三连咯 你得点赞是对歌谣最大的鼓励 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中…

使用WEB方式更改域用户帐户密码

使用WEB方式更改域用户帐户密码 <?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />1、这个只是域帐户密码的一种更改方式&#xff0c;正规来说&#xff0c;域用户帐户的密码更改方式可以有6种。今天介绍给大家的只是其中一种&…

一个路径下挂载(匹配)多个子组件

效果图如下 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><title>Document</title><script type"text/javascript" src"./lib/vue-2.4.0.js"></script><scrip…

JS之字符串截取函数substr

作用&#xff1a;substr() 方法可在字符串中抽取从 start 下标开始的指定数目的字符 语法&#xff1a;stringObject.substr(start,length) 参数1&#xff1a;必需。要抽取的子串的起始下标。必须是数值。如果是负数&#xff0c;那么该参数声明从字符串的尾部开始算起的位置。…

面向对象中的修饰关键词

final:用来修饰类和方法&#xff0c;修饰类的时候表示这个类是终极类&#xff0c;不能被其他类继承&#xff0c;修饰方法的时候&#xff0c;表示这个方法是终极方法&#xff0c;不能被子类重写。 static:用来修饰属性和方法&#xff0c;修饰属性的时候表示这个属性是静态属性&a…

GDB命令大全

GDB的使用   当程序出错并产生core 时   快速定位出错函数的办法   gdb 程序名 core文件名(一般是core,也可能是core.xxxx)   调试程序使用的键   r run 运行.程序还没有运行前使用   c cuntinue 继续运行。运行中断后继续运行   q 退出   kill 终止调…

Leetcode刷题(2)回文数

最好的种树是十年前,其次是现在。歌谣 每天一个前端小知识 提醒你改好好学习了 知乎博主 csdn博主 b站博主 放弃很容易但是坚持一定很酷 我是歌谣 喜欢就一键三连咯 你得点赞是对歌谣最大的鼓励 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &…

ZT Web Control 开发系列(一) 页面的生命周期

http://www.cnblogs.com/joeliu/category/143125.htmlPage是WebForm编程基本元素&#xff0c;它从TemplateControl派生&#xff0c;而TemplateControl又从Control派生&#xff0c;所以Page实际就是一个Control。同时Page也实现了IHttpHandler接口&#xff0c;所以它可以接受Htt…

计算属性computed的使用

效果图 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><title>Document</title><script type"text/javascript" src"./lib/vue-2.4.0.js"></script></head>…

JS之字符串截取方法substring

作用&#xff1a;substring() 方法用于提取字符串中介于两个指定下标之间的字符 语法&#xff1a;stringObject.substring(start,stop) 参数1&#xff1a;必需。一个非负的整数&#xff0c;规定要提取的子串的第一个字符在 stringObject 中的位置 参数2&#xff1a;可选。一…

gdb命令手册

GDB 的命令很多&#xff0c;本文不会全部介绍&#xff0c;仅会介绍一些最常用的。在介绍之前&#xff0c;先介绍GDB中的一个非常有用的功能&#xff1a;补齐功能。它就如同Linux下SHELL中的命令补齐一样。当你输入一个命令的前几个字符&#xff0c;然后输入TAB键&#xff0c;如…

HTML5增加的几个新的标签

HTML5又2008年诞生&#xff0c;HTML5大致可以等同于htmlcss3javascriptapi.... so --->支持css3强大的选择器和动画以及javascript的新的函数 先来记录一下吧&#xff01; 1。 <canvas>画布标签 HTML5的新标签 举例&#xff1a; 1 <html>2 <head>3 …