具有超时、最大大小和连接池的 http 请求

Posted

技术标签:

【中文标题】具有超时、最大大小和连接池的 http 请求【英文标题】:http request with timeout, maximum size and connection pooling 【发布时间】:2014-06-24 05:18:33 【问题描述】:

我正在寻找一种在 Python (2.7) 中执行具有 3 个要求的 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)

请求:没有最大尺寸

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:从未让“读取”方法工作,即使是 50Mo 文件...

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

我简直不敢相信 urllib2 是我可以使用的最好的 HTTP 库!因此,如果有人知道什么库可以做到这一点或如何使用以前的库之一......

编辑:

感谢 Martijn Pieters,我找到了最好的解决方案(即使对于大文件,StringIO 也不会减慢速度,其中 str 的添加会做很多事情)。

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()

【问题讨论】:

ctt.write(chunk) 行我得到一个TypeError: string argument expected, got 'bytes' 【参考方案1】:

你可以用requests 来做就行了;但您需要知道raw 对象是urllib3 胆量的一部分,并利用HTTPResponse.read() call 支持的额外参数,它可以让您指定要读取解码 数据:

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() generator version:

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() 调用已经让您可以访问相关内容,因此确实没有必要。

【讨论】:

谢谢。我试图比较哪种方法效果最好(特别是,哪一种限制了实际大小而不是下载的):urllib2 不接受压缩,r.raw.read 比较压缩后的大小,r.iter_content 比较实际大小但是真的会减慢代码的速度(也许流会使其更快)。 @AurélienLambert:r.iter_content() 减慢代码的速度完全取决于读取的块的大小;小块大小需要更多的循环迭代。它在已经的流上运行。 对于在 Python3 上尝试此功能的任何人,请注意您需要 content = b'' +1 @AurélienLambert:提防gzip bombs——我不知道上面的decode_content=True 是否会使代码易受攻击。无关:如果您愿意,可以read compressed data using urllib2 if you read it into memory as in your case。 Python 3 code allows to stream gzipped content. @J.F.Sebastian: decode_content=True 允许进行与response.contentresponse.text 属性完全相同的解压缩处理(将整个内容加载为一个二进制或Unicode 字符串)。无论哪种情况,所有解压都在 urllib3 中处理,其中不包含针对解压炸弹的保护。

以上是关于具有超时、最大大小和连接池的 http 请求的主要内容,如果未能解决你的问题,请参考以下文章

HttpClient连接池的连接保持超时和失效机制

HttpClient连接池的连接保持超时和失效机制

超时时间已到。超时时间已到,但是尚未从池中获取连接。出现这种情况可能是因为所有池连接均在使用,并且达到了最大池大小。

HttpClient连接池

rails 数据库连接池的工作原理

IIS连接数IIS并发连接数IIS最大并发工作线程数应用程序池的队列长度应用程序池的