一、Http协议中的chunked
一般http通信时会使用content_length头信息来表示服务器发送的文档内容长度,该头信息定义域http1.0协议RFC 1945 10.4章节中,浏览器接收到此头信息后接受完content_length中定义的产度字节后开始解析页面,但是如果服务器端有部分数据延迟发送则会出现浏览器白屏,用户体验差。
解决方案是在http1.1协议中EFC 2616中14.41章节中定义的Transfer_encoding:chunked的头信息。chunked编码定义在3.6.1中,所有HTTP1.1都应该支持使用chunked编码动态的提供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功能:
php:ob_flush();flush()
perl:STDOUT->autoflush(1);
java:out.flush()
python:sys.stout.flush()
ruby:stdout.flush
采用http1.1的transfer_encoding:chunked并且把IO的buffer flush下来,一遍浏览器更早的下载页面配套资源,不可能在头中包含content_length域来指明报文体长度,此时就需要通过transfer_encoding域来确定报文体长度
二、Chunked编码的格式
chunked编码一般使用若干个chunk串联而成,最后一个表明长度为0的chunk表示结束。每一个chunked分为头部和正文两部分,头部内容制定下一段正文的字符总数(非零开头的十六进制数字)和数量单位(一般不写,表示字节),正文部分就是指定长度的实际内容,两部分之间用回车换行(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-headerCRLF)
在数据部分有可能有注释字节,注释字节是分号分隔,如果发现注释的字节应该跳过。
摘自rfc:
A server using chunked transfer-coding in aresponse MUST NOT use the trailer for any header fields unless at least one ofthe following is true:
使用transfer_encoding作为响应的服务器不能使用trailer作为任何头部域,除非以下条件满足一条:
a)the request included a TE header fieldthat indicates "trailers" is acceptable in the transfer-coding of theresponse, as described in section 14.39; or,
a)请求包含一个TE头部域表示接收方可以接受trailers,请参见14.39章节
b)the server is the origin server for theresponse, the trailer fields consist entirely of optional metadata, and therecipient could use the message (in a manner acceptable to the origin server)without receiving this metadata. In other words, the origin server is willingto accept the possibility that the trailer fields might be silently discardedalong the path to the client.
服务器是原始响应服务器,trailer域包括所有可选数据,并且接受者可以使用消息(在某种意义上可以说接受原始服务器)而不用接受这些数据。换句话说原始服务器愿意接受trailer域可能在传递给客户端的过程中被默默地废弃掉
This requirement prevents aninteroperability failure when the message is being received by an HTTP/1.1 (orlater) proxy and forwarded to an HTTP/1.0 recipient. It avoids a situationwhere compliance with the protocol would have necessitated a possibly infinitebuffer on the proxy.
这样的设计阻止了这种失败:消息被一个http1.1或者更高版本的代理服务器转发给一个http1.0的接受者产生的失败。从而避免了这种情况:遵守协议的代理服务器必须拥有无限的buffer
An example process for decoding aChunked-Body is presented in appendix 19.4.6.
附件19.4.6中会给出一个解码chunked_body的http响应的例子
All HTTP/1.1 applications MUST be able toreceive and decode the "chunked" transfer-coding, and MUST ignorechunk-extension extensions they do not understand.
所有的http1.1应用必须能够接受和解码chunked transfer_encoding,而且在没有事先约定时必须忽略chunk_extension扩展
下面是抓包分析的chunked下载附件示例:
上面的一个chunk包没有包含chunk_extension。在chunksize之后就是\r\n。下面是最后一个chunk:
上述解释过于官方,简而言之,chunked编码的基本方法是将大块数据分解成多块小数据,每块都可以自指定长度,其具体格式如下:
其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);
return0;
}
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';