修复 httpx async with client.post() 的隐藏坑
· 阅读需 2 分钟
在构建多服务协作的 SaaS 系统时遇到此问题,记录根因与解法。
TL;DR
httpx.AsyncClient 不要用 async with client.post() 模式,应该先创建 client 再调用方法:response = await client.post()。
问题现象
import httpx
async def call_api():
async with httpx.AsyncClient() as client:
async with client.post(url, json=data) as response: # 问题代码
return response.json()
这段代码有时正常,有时报错:
httpx.RemoteProtocolError: cannot write to closing transport
RuntimeError: Session is closed
根因
async with client.post() 的陷阱
client.post() 返回的是 Response 对象,不是上下文管理器。用 async with 包装会导致:
- 连接过早关闭:
async with块结 束时立即关闭连接,但响应可能还在读取 - 资源竞争:多个并发请求时,连接池状态混乱
正确理解 httpx 上下文管理器
# ✅ 正确:client 是上下文管理器
async with httpx.AsyncClient() as client:
response = await client.post(url, json=data)
return response.json()
# ❌ 错误:把 response 当上下文管理器
async with client.post(url) as response:
...
解决方案
方案 1:单次请求(推荐简单场景)
async def call_api(url: str, data: dict) -> dict:
async with httpx.AsyncClient() as client:
response = await client.post(url, json=data)
response.raise_for_status()
return response.json()
方案 2:复用 client(推荐高频请求)
# 全局或依赖注入
_client = httpx.AsyncClient(timeout=30.0)
async def call_api(url: str, data: dict) -> dict:
response = await _client.post(url, json=data)
response.raise_for_status()
return response.json()
# 应用关闭时
async def shutdown():
await _client.aclose()
方案 3:FastAPI 依赖注入
from fastapi import Depends
from httpx import AsyncClient
async def get_http_client() -> AsyncClient:
async with AsyncClient(timeout=30.0) as client:
yield client
@router.post("/proxy")
async def proxy(
data: dict,
client: AsyncClient = Depends(get_http_client)
):
response = await client.post("https://external.api/endpoint", json=data)
return response.json()
FAQ
Q: httpx async with 怎么用才对?
A: async with 只用于管理 AsyncClient 生命周期,不是包装单个请求。正确模式:async with AsyncClient() as client: response = await client.post(...)。
Q: 为什么有时 async with client.post() 也能跑?
A: 单线程、低并发时可能碰巧正常,但高并发或网络延迟时会暴露问题。这是隐藏 bug,不要侥幸。
Q: httpx 超时怎么 配置?
A: AsyncClient(timeout=30.0) 或 AsyncClient(timeout=httpx.Timeout(connect=5.0, read=30.0))。