带超时、最大大小和连接池的HTTP请求

13 投票
1 回答
18695 浏览
提问于 2025-04-18 05:39

我在找一种方法,想在Python(2.7)中进行HTTP请求,并且有三个要求:

  • 超时设置(为了可靠性)
  • 内容最大大小(为了安全性)
  • 连接池(为了性能)

我检查了几乎所有的Python HTTP库,但没有一个能满足我的要求。例如:

urllib2:不错,但没有连接池

import urllib2
import json

r = urllib2.urlopen('https://github.com/timeline.json', timeout=5)
content = r.read(100+1)
if len(content) > 100: 
    print 'too large'
    r.close()
else:
    print json.loads(content)

r = urllib2.urlopen('https://github.com/timeline.json', timeout=5)
content = r.read(100000+1)
if len(content) > 100000: 
    print 'too large'
    r.close()
else:
    print json.loads(content)

requests:没有最大大小的限制

import requests
r = requests.get('https://github.com/timeline.json', timeout=5, stream=True)
r.headers['content-length'] # does not exists for this request, and not safe
content = r.raw.read(100000+1)
print content # ARF this is gzipped, so not the real size
print json.loads(content) # content is gzipped so pretty useless
print r.json() # Does not work anymore since raw.read was used

urllib3:即使是50MB的文件,我也从来没能让“read”方法正常工作……

httplib:httplib.HTTPConnection不是一个连接池(只有一个连接)

我几乎不敢相信urllib2是我能用的最好的HTTP库!所以如果有人知道哪个库可以做到这一点,或者如何使用之前提到的库……

编辑:

我找到的最佳解决方案是Martijn Pieters提供的(StringIO即使对于超大文件也不会变慢,而字符串拼接会很慢)。

r = requests.get('https://github.com/timeline.json', stream=True)
size = 0
ctt = StringIO()


for chunk in r.iter_content(2048):
    size += len(chunk)
    ctt.write(chunk)
    if size > maxsize:
        r.close()
        raise ValueError('Response too large')

content = ctt.getvalue()

1 个回答

20

你可以用 requests 来完成这个任务,没问题;不过你需要知道 raw 对象是 urllib3 的一部分,并且要利用 HTTPResponse.read() 这个方法 支持的额外参数,这样你就可以指定想要读取的是 解码过的 数据:

import requests
r = requests.get('https://github.com/timeline.json', timeout=5, stream=True)

content = r.raw.read(100000+1, decode_content=True)
if len(content) > 100000:
    raise ValueError('Too large a response')
print content
print json.loads(content)

另外,你也可以在读取之前设置 raw 对象的 decode_content 标志:

import requests
r = requests.get('https://github.com/timeline.json', timeout=5, stream=True)

r.raw.decode_content = True
content = r.raw.read(100000+1)
if len(content) > 100000:
    raise ValueError('Too large a response')
print content
print json.loads(content)

如果你不想直接去碰 urllib3 的内部实现,可以使用 response.iter_content() 来分块读取解码后的内容;这个方法也会使用底层的 HTTPResponse(通过 .stream() 生成器版本):

import requests

r = requests.get('https://github.com/timeline.json', timeout=5, stream=True)

maxsize = 100000
content = ''
for chunk in r.iter_content(2048):
    content += chunk
    if len(content) > maxsize:
        r.close()
        raise ValueError('Response too large')

print content
print json.loads(content)

这里有一个微妙的区别在于如何处理压缩数据的大小;r.raw.read(100000+1) 只会读取 100k 字节的压缩数据;而未压缩的数据会根据你的最大大小进行测试。iter_content() 方法会在 压缩流的大小 大于 未压缩数据的情况下 读取更多未压缩的数据。

这两种方法都不能让 r.json() 工作;因为 response._content 属性并没有被这些方法设置。当然,你可以手动设置。不过,由于 .raw.read().iter_content() 方法已经可以让你访问到相关内容,所以其实没有必要这样做。

撰写回答