aiohttp 请求生命周期对比requests库使用的区别
当你第一次使用 aiohttp 时,你会注意到一个简单的 HTTP 请求不是一次执行的,而是最多三个步骤:
async with aiohttp.ClientSession() as session:
async with session.get('http://python.org') as response:
print(await response.text())当来自其他库时尤其出乎意料,例如非常流行的requests,其中”hello world”看起来像这样:
response = requests.get('http://python.org')
print(response.text)那么为什么 aiohttp 片段如此冗长呢?
requests 库实际上也提供了一个会话系统。事实上,它可以让你做到:
with requests.Session() as session:
response = session.get('http://python.org')
print(response.text)这不是默认行为,也没有在文档的早期宣传。正因为如此,大多数用户的性能都会受到影响,但可以很快开始黑客攻击。对于请求,这是一个可以理解的权衡,因为它的目标是成为“人类的 HTTP”,而在这种情况下,简单性总是比性能更重要。 但是,如果使用 aiohttp,则选择异步编程,这是一种进行相反权衡的范式:更冗长以获得更好的性能。因此库默认行为反映了这一点,鼓励您从一开始就使用性能最佳实践。
默认情况下,该aiohttp.ClientSession对象将拥有一个最多具有 100 个连接的连接器,将其余连接放入队列中。这是一个相当大的数字,这意味着您必须同时连接到一百个不同的服务器(不是页面!),然后才能考虑您的任务是否需要资源调整。
事实上,您可以将会话对象想象为用户启动和关闭浏览器:每次您想要加载新选项卡时都这样做是没有意义的。
因此,您应该重用会话对象并从中发出许多请求。对于大多数脚本和中等大小的软件,这意味着您可以创建一个会话,并在程序的整个执行过程中重复使用它。您甚至可以将会话作为函数中的参数传递。例如,典型的“hello world”:
import aiohttp
import asyncio
async def main():
async with aiohttp.ClientSession() as session:
async with session.get('http://python.org') as response:
html = await response.text()
print(html)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())可以变成这样:
import aiohttp
import asyncio
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
html = await fetch(session, 'http://python.org')
print(html)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())在更复杂的代码库上,您甚至可以创建一个中央注册表来保存来自代码中任何位置的会话对象,或者Client保存对它的引用的更高级别的类。
那么何时创建多个会话对象呢?当您需要更精细的资源管理时,就会出现这种情况:
当在块ClientSession结束时 (或通过直接调用)关闭时,由于 asyncio 内部细节,底层连接保持打开状态。在实践中,底层连接将在片刻后关闭。但是,如果事件循环在底层连接关闭之前停止, 则会发出警告(启用警告时)。async withClientSession.close()ResourceWarning: unclosed transport
为了避免这种情况,必须在关闭事件循环之前添加一个小的延迟,以允许任何打开的底层连接关闭。
对于ClientSession没有 SSL 的情况,一个简单的零睡眠 ( ) 就足够了:await asyncio.sleep(0)
async def read_website():
async with aiohttp.ClientSession() as session:
async with session.get('http://example.org/') as resp:
await resp.read()
loop = asyncio.get_event_loop()
loop.run_until_complete(read_website())
# Zero-sleep to allow underlying connections to close
loop.run_until_complete(asyncio.sleep(0))
loop.close()对于ClientSession使用 SSL,应用程序必须在关闭前等待一小段时间:
...
# Wait 250 ms for the underlying SSL connections to close
loop.run_until_complete(asyncio.sleep(0.250))
loop.close()请注意,等待的适当时间量因应用程序而异。 如果这最终会在 asyncio 内部发生变化时变得过时,以便 aiohttp 本身可以等待底层连接关闭。
2022年第 1 期《Python 测试平台开发》课程
2022年第 10 期《python接口web自动化+测试开发》课程,2月13号开学