如何在python的http.client中读取一个响应块?

6 投票
2 回答
13412 浏览
提问于 2025-04-18 11:39

在Python 3.3及以上版本中,使用http.client(或者其他内置的Python HTTP客户端库),我该如何一次读取一个HTTP分块的响应呢?

我正在扩展一个现有的测试工具(用http.client写的),这个工具是为了一个使用HTTP分块传输编码的服务器。为了简单起见,我希望每当客户端收到一个HTTP分块时,都能打印一条消息。

我的代码遵循了一个比较标准的模式来读取较大的响应:

conn = http.client.HTTPConnection(...)
conn.request(...)
response = conn.getresponse()

resbody = []

while True:
    chunk = response.read(1024)
    if len(chunk):
        resbody.append(chunk)
    else:
        break

conn.close();

但是这样读取的总是1024字节的分块,不管服务器是发送10字节的分块还是10MiB的分块。

我想要的效果应该像下面这样:

while True:
    chunk = response.readchunk()
    if len(chunk):
        resbody.append(chunk)
    else
        break

如果用http.client实现不了,那用其他内置的HTTP客户端库可以吗?如果内置的客户端库也不行,那用pip安装的模块可以吗?

2 个回答

6

更新:

分块传输编码的好处在于,它可以让我们传输动态生成的内容。至于一个HTTP库是否允许你读取单独的块,这又是另一个问题(可以参考RFC 2616 - 第3.6.1节)。

我能理解你想做的事情是有用的,但标准的Python HTTP客户端库并不能直接满足你的需求,可能需要一些小技巧(可以查看http.clienthttplib)。

你想做的事情在你的测试环境中可能没问题,但在实际使用中就没有保证了。你的客户端读取的数据块和服务器发送的数据块可能会有不同的分块方式。例如,数据在到达之前可能已经被一个代理服务器重新分块过(可以参考RFC 2616 - 第3.2节 - 分帧技术)。


诀窍是告诉响应对象它不是分块的(resp.chunked = False),这样它就会返回原始字节。这让你可以在返回时解析每个块的大小和数据。

import http.client

conn = http.client.HTTPConnection("localhost")
conn.request('GET', "/")
resp = conn.getresponse()
resp.chunked = False

def get_chunk_size():
    size_str = resp.read(2)
    while size_str[-2:] != b"\r\n":
        size_str += resp.read(1)
    return int(size_str[:-2], 16)

def get_chunk_data(chunk_size):
    data = resp.read(chunk_size)
    resp.read(2)
    return data

respbody = ""
while True:
    chunk_size = get_chunk_size()
    if (chunk_size == 0):
        break
    else:
        chunk_data = get_chunk_data(chunk_size)
        print("Chunk Received: " + chunk_data.decode())
        respbody += chunk_data.decode()

conn.close()
print(respbody)
6

我发现用requests库这样做更简单

r = requests.post(url, data=foo, headers=bar, stream=True)

for chunk in (r.raw.read_chunked()):
    print(chunk)

撰写回答