内容编码:gzip + Transfer-Encoding:使用gzip / zlib进行分块会产生错误的标头检查

时间:2014-03-05 13:48:53

标签: python python-3.x gzip zlib transfer-encoding

如何使用gzip编码管理分块数据? 我有一台服务器以下列方式发送数据:

HTTP/1.1 200 OK\r\n
...
Transfer-Encoding: chunked\r\n
Content-Encoding: gzip\r\n
\r\n
1f50\r\n\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\xec}\xebr\xdb\xb8\xd2\xe0\xef\xb8\xea\xbc\x03\xa2\xcc\x17\xd9\xc7\xba\xfa\x1e\xc9r*\x93\xcbL\xf6\xcc\x9c\xcc7\xf1\x9c\xf9\xb6r\xb2.H ... L\x9aFs\xe7d\xe3\xff\x01\x00\x00\xff\xff\x03\x00H\x9c\xf6\xe93\x00\x01\x00\r\n0\r\n\r\n

我有一些不同的方法,但我忘了这里。

data = b''
depleted = False
while not depleted:
    depleted = True
    for fd, event in poller.poll(2.0):
        depleted = False
        if event == select.EPOLLIN:
            tmp = sock.recv(8192)
            data += zlib.decompress(tmp, 15 + 32)

给出(也尝试仅在\r\n\r\n obv之后解码数据):
zlib.error: Error -3 while decompressing data: incorrect header check

所以我认为一旦数据以整个格式收到,数据应该被解压缩。

        ...
        if event == select.EPOLLIN:
            data += sock.recv(8192)
data = zlib.decompress(data.split(b'\r\n\r\n',1)[1], 15 + 32)

同样的错误。还尝试解压缩data[:-7],因为数据末尾的块ID以及data[2:-7]和其他各种组合,但具有相同的错误。

我也通过以下方式尝试了gzip模块:

with gzip.GzipFile(fileobj=Bytes(data), 'rb') as fh:
    fh.read()

但是这给了我“不是一个gzip压缩文件”。

即使将服务器(标题+数据)收到的数据记录下来,然后在服务器(标题+数据)上记录下来,然后在端口80上创建一个服务器套接字,为浏览器提供数据(再次,按照原样),它完全呈现数据完好无损。 我拿走了这些数据,剥离了标题(没有别的)并在文件上尝试了gzip: enter image description here

感谢@ mark-adler我生成了以下代码来取消分块数据:

unchunked = b''
pos = 0
while pos <= len(data):
    chunkLen = int(binascii.hexlify(data[pos:pos+2]), 16)
    unchunked += data[pos+2:pos+2+chunkLen]
    pos += 2+len('\r\n')+chunkLen

with gzip.GzipFile(fileobj=BytesIO(data[:-7])) as fh:
    data = fh.read()

这会产生OSError: CRC check failed 0x70a18ee9 != 0x5666e236,距离更近一步。简而言之,我根据以下四个部分剪辑数据:

  • <chunk length o' X bytes> \r\n <chunk> \r\n

我可能会到达那里,但还不够近。

脚注:是的,套接字远非最佳,但它看起来是这样的,因为我认为我没有从套接字获取所有数据,所以我实现了一个巨大的超时和尝试使用depleted进行故障保护:)

2 个答案:

答案 0 :(得分:3)

由于压缩数据可能包含,因此无法拆分\r\n,如果时间足够长,肯定会包含该序列。您需要首先使用提供的长度(例如,第一个长度1f50)进行解除,并将生成的块提供给解压缩。压缩数据以\x1f\x8b开始。

分块是十六进制数字,crlf,chunk,包含许多字节,crlf,十六进制数字,crlf,chunk,crlf,...,最后一个chunk(零长度),[可能是一些标题],crlf。

答案 1 :(得分:1)

@ mark-adler给了我关于HTML协议中的分块模式如何工作的一些很好的指示,除此之外,我用不同的方式解开解压缩数据。

  1. 你应该将这些块拼接成一个大堆
  2. 您应该使用gzip而不是zlib
  3. 您只能解压缩整个拼接块,部分内容无法正常工作
  4. 以上是上述所有三个问题的解决方案

    unchunked = b''
    pos = 0
    while pos <= len(data):
        chunkNumLen = data.find(b'\r\n', pos)-pos
    #   print('Chunk length found between:',(pos, pos+chunkNumLen))
        chunkLen=int(data[pos:pos+chunkNumLen], 16)
    #   print('This is the chunk length:', chunkLen)
        if chunkLen == 0:
    #       print('The length was 0, we have reached the end of all chunks')
            break
        chunk = data[pos+chunkNumLen+len('\r\n'):pos+chunkNumLen+len('\r\n')+chunkLen]
    #   print('This is the chunk (Skipping',pos+chunkNumLen+len('\r\n'),', grabing',len(chunk),'bytes):', [data[pos+chunkNumLen+len('\r\n'):pos+chunkNumLen+len('\r\n')+chunkLen]],'...',[data[pos+chunkNumLen+len('\r\n')+chunkLen:pos+chunkNumLen+len('\r\n')+chunkLen+4]])
        unchunked += chunk
        pos += chunkNumLen+len('\r\n')+chunkLen+len('\r\n')
    
    with gzip.GzipFile(fileobj=BytesIO(unchunked)) as fh:
        unzipped = fh.read()
    
    return unzipped
    

    我将调试输出留在那里,但由于某种原因没有注释 它甚至非常有用,即使它看起来像是一团糟,你/我实际上试图解压缩哪些数据以及哪些部分被取出在哪里以及每个计算带来的第四个值。

    此代码将使用以下格式遍历分块数据:
    <chunk length o' X bytes> \r\n <chunk> \r\n

    首先提取X bytes 1f50时我必须小心谨慎,binascii.hexlify(data[0:4])我首先必须先使用int()才能将gzip放入.GzipFile(...),而不是def http_gzip(data): compressed = gzip.compress(data) # format(49, 'x') returns `31` which is `\x31` but without the `\x` notation. # basically the same as `hex(49)` but ment for these kind of things. return bytes(format(len(compressed), 'x')),'UTF-8') + b'\r\n' + compressed + b'\r\n0\r\n\r\n' 确定为什么我不再需要它,因为我需要它才能获得~8000的长度然后它突然给了我一个非常大的数字,这是不合逻辑的,即使我没有真正给它任何其他数据..无论如何。 之后,只需要确保数字是正确的,然后将所有块组合成一堆{{1}}数据并将其输入{{1}}。

    3年后编辑:

    我知道这首先是客户端问题,但这是一个服务器端函数来发送一些功能测试:

    {{1}}