杰瑞科技汇

Python httplib如何实现异步请求?

http.client 本身是同步的。

Python httplib如何实现异步请求?-图1
(图片来源网络,侵删)

它是一个阻塞式的库,当你调用 request(), getresponse() 等方法时,当前线程会一直等待,直到网络请求完成或发生超时/错误,在等待期间,你的程序无法执行其他任何任务。

要实现异步 HTTP 请求,我们不能直接使用 http.client,而是需要将它与异步编程模型(如 asyncio)结合使用,这通常意味着在一个单独的线程中运行同步的 http.client 代码,然后通过 asyncio 的机制将其结果“回调”给主异步事件循环。

下面我将介绍几种实现异步 HTTP 请求的方法,从最基础的(手动封装 http.client)到现代推荐的(使用专门的异步库)。


手动封装 http.client(使用 run_in_executor

这是最直接的方法,利用 asynciorun_in_executor 将同步的 http.client 调用放到一个线程池中执行,从而避免阻塞事件循环。

Python httplib如何实现异步请求?-图2
(图片来源网络,侵删)

工作原理:

  1. 你的异步函数 (async def) 在事件循环中运行。
  2. 当需要进行 HTTP 请求时,调用 await loop.run_in_executor(executor, sync_function, *args)
  3. asyncio 会从线程池中拿出一个空闲线程,在其中执行 sync_function
  4. 原来的异步函数被挂起,事件循环可以去处理其他任务。
  5. sync_function 执行完毕(即 HTTP 请求完成),线程会返回结果,asyncio 会唤醒挂起的异步函数,并将结果返回。

示例代码:

import asyncio
import http.client
import concurrent.futures
# 1. 定义同步的 HTTP 请求函数
def fetch_data_sync(url, path):
    """
    使用 http.client 发送同步 GET 请求
    """
    print(f"[同步] 开始请求: {url}{path}")
    try:
        # 创建连接 (根据 URL 选择 http 或 https)
        if url.startswith("https://"):
            conn = http.client.HTTPSConnection(url)
        else:
            conn = http.client.HTTPConnection(url)
        # 发送请求
        conn.request("GET", path)
        # 获取响应
        response = conn.getresponse()
        # 读取响应数据
        data = response.read()
        # 关闭连接
        conn.close()
        print(f"[同步] 请求成功,状态码: {response.status}")
        return data.decode('utf-8')
    except Exception as e:
        print(f"[同步] 请求失败: {e}")
        return None
# 2. 定义异步函数,并使用 run_in_executor
async def fetch_data_async(url, path):
    """
    异步获取数据,通过 run_in_executor 调用同步函数
    """
    # 获取当前事件循环
    loop = asyncio.get_running_loop()
    # 创建一个线程执行器 (默认会使用 ThreadPoolExecutor)
    # 也可以自定义: executor = concurrent.futures.ThreadPoolExecutor(max_workers=5)
    # 然后在 run_in_executor 中传入 executor
    # 将同步任务提交到线程池执行
    # fetch_data_sync 是要执行的函数,url 和 path 是它的参数
    data = await loop.run_in_executor(None, fetch_data_sync, url, path)
    return data
# 3. 主程序入口
async def main():
    # 模拟并发请求
    urls_to_fetch = [
        ("httpbin.org", "/get"),
        ("httpbin.org", "/delay/2"), # 这个接口会延迟2秒返回
        ("httpbin.org", "/status/200"),
    ]
    # 使用 asyncio.gather 并发执行多个异步任务
    tasks = [fetch_data_async(url, path) for url, path in urls_to_fetch]
    results = await asyncio.gather(*tasks)
    print("\n--- 所有请求完成 ---")
    for i, result in enumerate(results):
        print(f"任务 {i+1} 的结果长度: {len(result) if result else 'N/A'}")
if __name__ == "__main__":
    asyncio.run(main())

优点:

  • 可以直接复用现有的 http.client 代码。
  • 原理清晰,是理解异步与同步结合的绝佳例子。

缺点:

  • 线程开销:每个并发请求都会占用一个线程,在需要极高并发的场景下(如数千个请求),线程可能会成为瓶颈。
  • 代码冗余:需要为每个同步的 HTTP 客户端代码写一个异步的包装器,比较繁琐。
  • 连接管理:手动管理连接的创建和关闭,容易出错,理想情况下应该使用连接池。

使用现代异步 HTTP 客户端库(推荐)

对于异步编程,社区已经开发出了非常成熟且高效的异步 HTTP 客户端,它们原生支持 asyncio,性能远超手动封装的同步库。

最流行的两个库是:

  1. httpx: 现代的选择,功能强大,API 友好,支持 HTTP/1.1 和 HTTP/2。
  2. aiohttp: 老牌的异步 HTTP 客户端/服务器库,在 asyncio 生态中非常流行。

推荐方案:使用 httpx

httpx 的 API 设计非常现代化,与流行的 requests 库非常相似,学习成本低。

安装:

pip install httpx

示例代码:

import asyncio
import httpx
async def fetch_data_with_httpx(url, path):
    """
    使用 httpx 进行异步 GET 请求
    """
    print(f"[httpx] 开始请求: {url}{path}")
    try:
        # httpx.AsyncClient 是一个异步上下文管理器,推荐使用
        # 它会自动管理连接池,性能更好
        async with httpx.AsyncClient() as client:
            response = await client.get(f"{url}{path}")
            # response.raise_for_status() # 如果状态码不是 2xx,则抛出异常
            print(f"[httpx] 请求成功,状态码: {response.status_code}")
            return response.text
    except httpx.RequestError as e:
        # 捕获 httpx 的请求错误
        print(f"[httpx] 请求失败: {e}")
        return None
async def main():
    urls_to_fetch = [
        "http://httpbin.org/get",
        "http://httpbin.org/delay/2",
        "http://httpbin.org/status/200",
    ]
    # 使用 asyncio.gather 并发执行
    tasks = [fetch_data_with_httpx(url) for url in urls_to_fetch]
    results = await asyncio.gather(*tasks)
    print("\n--- 所有请求完成 ---")
    for i, result in enumerate(results):
        print(f"任务 {i+1} 的结果长度: {len(result) if result else 'N/A'}")
if __name__ == "__main__":
    asyncio.run(main())

优点:

  • 原生异步:代码简洁、高效,没有线程开销。
  • 连接池httpx.AsyncClient 内置了连接池,复用 TCP 连接,性能极佳。
  • 现代特性:支持 HTTP/2、WebSocket 等。
  • 优秀的 API:与 requests 类似,非常易用。

备选方案:使用 aiohttp

aiohttp 是另一个非常强大的选择,尤其在构建异步 Web 服务时非常流行。

安装:

pip install aiohttp

示例代码:

import asyncio
import aiohttp
async def fetch_data_with_aiohttp(session, url):
    """
    使用 aiohttp 进行异步 GET 请求
    注意:session 需要在外部创建和管理
    """
    print(f"[aiohttp] 开始请求: {url}")
    try:
        async with session.get(url) as response:
            response.raise_for_status() # 检查响应状态
            text = await response.text()
            print(f"[aiohttp] 请求成功,状态码: {response.status}")
            return text
    except aiohttp.ClientError as e:
        print(f"[aiohttp] 请求失败: {e}")
        return None
async def main():
    urls_to_fetch = [
        "http://httpbin.org/get",
        "http://httpbin.org/delay/2",
        "http://httpbin.org/status/200",
    ]
    # aiohttp 推荐在外部创建一个 session 并在整个应用中复用
    # session 本身就是一个连接池
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_data_with_aiohttp(session, url) for url in urls_to_fetch]
        results = await asyncio.gather(*tasks)
    print("\n--- 所有请求完成 ---")
    for i, result in enumerate(results):
        print(f"任务 {i+1} 的结果长度: {len(result) if result else 'N/A'}")
if __name__ == "__main__":
    asyncio.run(main())

总结与对比

特性 http.client + run_in_executor httpx (推荐) aiohttp (备选)
类型 同步库,需手动封装 原生异步 原生异步
易用性 低,需要写包装器 ,API 类似 requests 中等,API 略有不同
性能 较低,受线程池限制 ,基于 asyncio ,基于 asyncio
连接管理 手动,易出错 自动 (连接池) 自动 (连接池)
现代特性 支持 HTTP/2, WebSocket 支持 WebSocket
适用场景 - 现有代码库无法替换,且需要快速引入异步。
- 学习原理。
- 新项目首选
- 需要 HTTP/2 或现代 API 的项目。
- 已在使用 aiohttp 生态的项目。
- 构建异步 Web 服务。

最终建议

对于任何新的 Python 异步项目,请直接使用 httpx

它提供了最佳的开发体验、性能和现代化特性,你几乎不需要再考虑直接使用 http.client 来进行异步编程,除非你有非常特殊的需求(在一个极其受限的环境中,无法安装第三方库,并且并发量很低)。

http.client 的主要价值在于它是 Python 标准库的一部分,作为理解 HTTP 协议和底层网络通信的基础,在生产级的异步应用中,httpxaiohttp 是无可争议的更优选择。

分享:
扫描分享到社交APP
上一篇
下一篇