博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
HTML中chunked解码和gzip解压
阅读量:2493 次
发布时间:2019-05-11

本文共 8338 字,大约阅读时间需要 27 分钟。

chunked编码

chunked编码的的好处

当访问的时动态页面时,服务器则无法预知内容的大小,因此需要一遍产生数据,一边发送数据,将数据分块发送(服务器通过响应头’Transfer-Encoding: chunked’告诉浏览器它将使用chunked编码传输)。浏览器也不需要等到内容字节全部下载完成,只要接收到一个chunked块就可解析页面,并且可以下载html中定义的页面内容,包括js,css,image等。

更多优点如下:

  • 1.允许服务器为动态生成的内容维持HTTP持久链接。

    通常,持久链接需要服务器在开始发送消息体前发送Content-Length消息头字段,但是对于动态生成的内容来说,在内容创建完之前是不可知的。

  • 2.允许服务器在最后发送消息头字段。

    对于那些头字段值在内容被生成之前无法知道的情形非常重要,例如消息的内容要使用散列进行签名,散列的结果通过HTTP消息头字段进行传输。没有分块传输编码时,服务器必须缓冲内容直到完成后计算头字段的值并在发送内容前发送这些头字段的值。

  • 3.HTTP服务器有时使用压缩 (gzip或deflate)以缩短传输花费的时间,分块传输编码可以用来分隔压缩对象的多个部分

    在这种情况下,块不是分别压缩的,而是整个负载进行压缩,压缩的输出使用本文描述的方案进行分块传输。在压缩的情形中,分块编码有利于一边进行压缩一边发送数据,而不是先完成压缩过程以得知压缩后数据的大小

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)
解释:

  • 1 Chunked-Body表示经过chunked编码后的报文体
    报文体可以分为chunk,last-chunk,trailer和结束符四部分。chunk的数量在报文体中最少可以为0,无上限;
  • 2 每个chunk的长度是自指定的
    起始的数据必然是16进制数字的字符串,代表后面chunk-data的长度(字节数)。这个16进制的字符串第一个字符如果是“0”,则表示chunk-size为0,该chunk为last-chunk,无chunk-data部分。
  • 3 可选的chunk-extension由通信双方自行确定,如果接收者不理解它的意义,可以忽略。
  • 4 trailer是附加的在尾部的额外头域,通常包含一些元数据(metadata, meta means “about information”),这些头域可以在解码后附加在现有头域之后

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

length := 0         //长度计数器置0read chunk-size, chunk-extension (if any) and CRLF      //读取chunk-size, chunk-extension和CRLFwhile(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,如果为空即只有CRLFwhile (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这个标记

chunked解码

解码目的将分块的chunk-data整合恢复成一块作为报文体,同时记录此块体的长度,length最后的值实际为所有chunk的chunk-size之和。

接收端就可以根据长度值读取数据,以及最后一个长度为0的块来判定接收结束

代码

# 模拟chunked块数据chunk1 = b'4\r\nWiki\r\n'chunk2 = b'6\r\npedia \r\n'chunk3 = b'E\r\nin \r\n\r\nchunks.\r\n'last_chunk = b'0\r\n'chunks = chunk1 + chunk2 + chunk3 + last_chunk
# 解码字节串形式的chunkeddef decode_chunked(content):    # 自定义chunked解码    newContent = b''    offset = 0    while True:        try:            pos = content.find(b'\r\n', offset)  # 找chunked块的前一个\r\n            chunk_size = int(content[offset: pos], 16)            if chunk_size > 0:                offset = pos + 2                newContent += content[offset: offset+chunk_size]                pos = content.find(b'\r\n', offset+chunk_size)   # 找chunked块的后一个\r\n                offset = pos + 2            else: break        except BaseException as ret:            print(f'没有达到最后一个chunked块!,{ret}')            break    return newContentprint(decode_chunked(chunks).decode('utf-8'))
# 解码数据流形式的chunkedfrom io import BytesIOclass chunk(object):    '''读取stream,输出解码chunked后的信息'''    def __init__(self, chunks_data):        self.content = chunks_data        def decode_chunked(self):        # chunked解码        buffer = b''        with BytesIO(self.content) as stream:            while True:                try:                    line = self.readline(stream)                    length = int(line[:-2], 16)                    if length:                        buffer += stream.read(length)                        # 读走数据末尾的'\r\n'                        stream.read(2)                    else:                         # 终止块                        return buffer                except BaseException as ret:                    print(f'没有达到最后一个chunked块!,{ret}')                    return buffer                    def readline(self, input_stream):        # 读取一行:以'\r\n'结尾        is_end = False        buffer = b''        while True:            byte = input_stream.read(1)            if not byte:                 # EOF返回空字符串                return buffer            buffer += byte            if byte == b'\r':                is_end = True            elif is_end:                 if byte == b'\n':                    return buffer                chunk(chunks).decode_chunked()

如何解压gzip格式的响应体?

gzip文件中存储的也是字节串,只不过是经过压缩过。如果直接用vim打开是乱码。

使用python的gzip模块可以将gzip格式的文件解压为txt文本;或者将gzip形式的字节串解压为原始字节串。

# 将以gzip压缩的响应体解压为原始字节串import gzipgz_bytes = b'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xa5V\xddo\xdb6\x10\x7f\xf7_\xc1r@\xba\x01\xa1\xe4:\x08\xe6\r\x96\x87\xcd[\xb7=u\x0f)\xb0\x01\x03\x02\x8a:Kt(R#)\x7f\x14\xfd\xe3w\x14%EM\xd2-\xa9\x1fl\x91\xc7\xdf}\xdf\x91\xb7z\xf5\xf3\xbb\xcd\xcd_\x7f\xfcB*_\xab\xf5l\x15>Dq]f\x144k\x1d%Bq\xe72j*A\xd73\x04\x00/\xd63BV^z\x05\xebw\x96\x0b\x05\xe47P\r\xd9\x80\xf6`Wi<\x99\x05P\r\x9e\x13Qq\xeb\xc0g\xf4\xfd\xcd[\xb6\xa4\xeb\xf1@\xf3\x1a2\xba\x97ph\x8c\xf5\xa8\xca\xa0\x00\xed\xefIl+}&\xcc\x1e\xec%9\xc8\xc2WY\x01{)\x80u\x9bK"\xb5\xf4\x92+\xe6\x04W\x90\xbd\x99\x88\xae\xbco\x18\xfc\xd3\xca}F\xffd\xef\x7fd\x1bS7\xdc\xcb\\\xc1D\x8f\x84\x0c\x8a\x12\x1e\x99T;\xde4J\nd0\x9a\xddH\x05\x1b\xa3\x8c\x9dp~\xb5\x15\xdb|\xcb\xff\x87\x13\xe1[YN\xd8R\xd7\xdc\nS\xd7F\xa7\xb91w\xccC\xdd(\xee!\xc5\xf0\xb2\xfe@\xd6ej\x98\x14\x01c\xcd\xc1\x81\x8db\x92c\xad\x9ea\xe9\xef5/\xe1\xcbUn\xf9>|\xd9\xe2\xdby\xd2\xe8\xf2\xa5\x0ej\xa3\x81\xa6\xf7L\x8d5\rX\x7f\xcah\x01NX\xd9\x04\xb6\t\xfeW\xf0^\xea\x928\xcf\xad\x87\x82\x94\xadD\xe0%)\x8chkDtj.\x89o\xbd\xb1\x98i<\xe1VT\xd2\x83\xf0\xad\r@\xae\x0bR\x1b\x0b\x83H\xb25\x96\xf4U\x89\xca\x8bVx\xd7\x810\x8e\xa1t\\BI\xb4OI}G,\xa8\x8c\xba\nkM\xb4\x9e\x04\xcf)\xa9,l3*\x9c\xc3\xb8`,\xdd\x10\x93\x04\xff\xe8C\xde\xc8\xe2O\r\xc6\xa6\x83\xa7!j\xc4\xc9\x0f\x80]\xf3\xe6\xbb\xc5\x11\x7f\x83\xcc/\xcc\x05J\xb8\xcf\xc5\xb3U/\x96G\xfc\x9d\xabz\xb1|\xb9\xea\xab\xc5\xf1\xea\\\x9f\xaf\x9et9\xd4\x1e0oZQ\xb1h\xc3\xe8\xed\x1c\xbd\x9d\x9f\xed\xed\xfceZ\xaf1\xbd\xd7g\xa7\xf7\xfa\x85\xbe.\xd1\xd7\xe5\xd9\xbe.\x07_W\xaf\x18\xc3\xabt\x87=e\xec\xf7\xbe\x82\x1a\x08cH\x9ft\x88?)p\x15\x80\x9f\xb6\x07W\x9e\xa7\xcbd\x9e\xcc\xd3\x03\xe4\xdd6\xa9\xa5N\xf0\x8c\x12Yt\xa0\xae\xdb:\r\xa0\x8bAI\'\xfe\xd3\x16|RA\x01\xb5aA,s\xd8\xf2l\x94\x1d\x0b\xcf\xc3\xd1\xa7\x83\x8agH\xc3\x80\x9e\xc3\x8e\xc1\xf5\xce[\xde$\xffe\xc7*\xder\xeb\x83\xd4\x859$\x98\x83R\x99\x9c+\xf2\xf1\xe3x\xa9%\x07\x8b\xee|\xfd\xba\xc7\x12g\x05\xa6\x11tZ\xf4\xe9\xda\xb94r%;G\xd7\x7f\x1f\xaf6i/\xf6\xf57\xaba\xb9J\xe3[<[\xe5\xa68\x8d\xef\xf4\x8ea.X(\x1e~2\xadg\xe10\x96V!\xf7]V\xa2\xe8\x9f\x02\xfd\xb3\\\r\xef\x9fE\xe4\x0bj\xc0\x12k\xf0\x8d\xa59\xd7\x1a\xec\xc8\xd8\xc3#\xa4g@\x16\x83\x8e\xb0v\xbe \xf1\x80u#AF\'c\x02E\xfb\x07T\xaf\'\x8d\xe0~\x17\xac\xad\xb9\xd4\x18|\xbc\xd9q1\xd1\x1a\xe8\x9b\x91:j\xfd\xe4\xd1`\xd1RT\xf3$y\xe0\xc1\x87\x0cJcO]s8D? \x0c\xb8-\xf0\xf0\xe2\x14lxT\x10\xfa\x986\xa0\x9dQm\xd0\xe6\x98\xc3\x82\xc7\x05\xa2\x1f\xd3\xeeeC\x91sq7\x01?"\x8dX\xac\xc21\x19\xfd\x9b\'\xf5\xd6\x8cQ@L\xab%\x8eL\x0e\x07\xa3\x1e=\xd8w\xcb?\xdc\xb6V\xc5b\x8b\xc3\x05W*\tC_\xc8\xc7C\xbeQi:\xddc@\xe5\xbe+\xa8\xb8\x08\xab\xbe\x90\'\xfd\xb0\xe3{\x1e\xa94\xd67\xd6\xb4\x92\xb9Km\x98\xca,\x0c\xdf\xae\xc2\xc7\x9a~\xbe\xacP\x02\x9f\xe5\xe5\xee\xa4\x11\xd6}z\x964\r\xe9\x0c\xcd\xe7m\xeb<$X{\xa96\x1eG\x82\x1f\n\xd3\x15\x9a\xe9\xa6\x86pp!2\x9c0"\xfeb\xe7\xb2<\xbf\x10\x85\xce\xde\\4\x02oX\t\x17\x91\xf1&\x98\x88g\xc1\xc8\x0c\xc5\x86\xb1\xcb\x1a\xe7pT)C\xe1N\x8d\x9b\xad\xd2\xd0\x89\xdd\xa2\x9b\xb5\xff\x05\x00\x88\x9d4{\x0b\x00\x00'print(gzip.decompress(gz_bytes))
# outputb'\n\n\n\n  Oracle Help Center\n\n  
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n\n\n\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n\n \n \n \n\n\n\n'

gzip + chunked处理

该模式下,响应体先用gzip压缩,然后再使用chunked编码分块(每个chunk本身没有压缩); 客户端收到响应体应先chunked解码为完整的gzip文件,再使用gzip解压. 测试文件:

# 对gzip+chunked的处理:先使用chunked解码,再使用gzip解压                with open('testFile/13_.htm', 'rb') as f:    cc = f.read()    cz = chunk(cc).decode_chunked()    print(gzip.decompress(cz))

参考资料

1.

转载地址:http://rghrb.baihongyu.com/

你可能感兴趣的文章
Hive面试题干货(亲自跟着做了好几遍,会了的话对面试大有好处)
查看>>
力扣题解-230. 二叉搜索树中第K小的元素(递归方法,中序遍历解决)
查看>>
力扣题解-123. 买卖股票的最佳时机 III(动态规划)
查看>>
Django 源码阅读:服务启动(wsgi)
查看>>
Django 源码阅读:url解析
查看>>
Docker面试题(一)
查看>>
第一轮面试题
查看>>
2020-11-18
查看>>
Docker面试题(二)
查看>>
一、redis面试题及答案
查看>>
消息队列2
查看>>
C++ 线程同步之临界区CRITICAL_SECTION
查看>>
测试—自定义消息处理
查看>>
MFC中关于虚函数的一些问题
查看>>
根据图层名获取图层和图层序号
查看>>
规范性附录 属性值代码
查看>>
提取面狭长角
查看>>
Arcsde表空间自动增长
查看>>
Arcsde报ora-29861: 域索引标记为loading/failed/unusable错误
查看>>
记一次断电恢复ORA-01033错误
查看>>