1

您如何使用 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)

Gives(也尝试在\r\n\r\nobv 之后仅解码数据):
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()

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

即使将服务器收到的数据(标头+数据)记录到文件中,然后在端口 80 上创建一个服务器套接字,将数据(再次按原样)提供给浏览器,它也能完美呈现,因此数据是完好无损的。我拿了这些数据,去掉了标题(没有别的),然后在文件上尝试了 gzip: 在此处输入图像描述

感谢@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:)进行故障保护

4

2 回答 2

3

您不能拆分,\r\n因为压缩数据可能包含,如果足够长,肯定会包含该序列。您需要先使用提供的长度(例如第一个长度1f50)进行解块,然后将生成的块提供给解压缩。压缩数据以\x1f\x8b.

分块是十六进制数,crlf,具有那么多字节的块,crlf,十六进制数,crlf,块,crlf,...,最后一个块(长度为零),[可能是一些标题],crlf。

于 2014-03-05T14:57:48.770 回答
1

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

  1. 你应该把大块拼接成一大堆
  2. 你不应该gzip使用zlib
  3. 您只能解压缩整个缝合的块,部分地解压是行不通的

以下是上述所有三个问题的解决方案:

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(),不知道为什么我不再需要它,因为我需要它才能获得大约 8000 的长度但是它突然给了我一个非常大的数字,即使我没有真正给它任何其他数据,这也不合逻辑..无论如何。之后,只需确保数字正确,然后将所有数据块组合成一大堆gzip数据并将其输入.GzipFile(...).

3年后编辑:

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

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'
于 2014-03-05T18:06:55.007 回答