asyncclient 必须显式关闭,否则连接泄漏;应使用 async with 或在 lifespan 中调用 aclose();timeout 需分阶段设置;http/2 需手动启用并确认全链路兼容;勿与 requests 混用 session;dns 缓存需主动管理。

AsyncClient 必须显式关闭,否则连接泄漏
异步客户端不自动回收连接池,httpx.AsyncClient 实例用完不关,会持续占用 socket 和 DNS 缓存,最终触发 ConnectionRefusedError 或 TimeoutError。常见于 FastAPI 路由里直接 new 一个 client 并返回——没 await client.aclose(),下次请求就可能卡住。
- 必须用
async with httpx.AsyncClient() as client:包裹,这是最安全的写法 - 若需复用 client(如全局单例),务必在应用退出时调用
await client.aclose(),比如在 FastAPI 的lifespan里注册 shutdown 事件 - 别在类的
__init__里直接创建 client,除非你明确控制其生命周期;更别把它塞进普通函数局部变量里还指望它自己清理
timeout 设置不当导致协程卡死或误判失败
默认 timeout 是 5 秒,但这个值对生产环境几乎总是太短或太模糊。不设 timeout 参数,实际走的是 httpx.Timeout(5.0),它只控制单次读/写,不控制整个请求耗时,容易出现“看起来没超时,但实际卡在 DNS 或 TLS 握手”。
- 显式传入
timeout=httpx.Timeout(connect=10.0, read=30.0, write=10.0, pool=5.0),把各阶段拆开控制 - 如果调用内部服务(如 Kubernetes 集群内网),可适当调高
connect(避免因 service mesh 初始化延迟被误杀) - 别用
timeout=None,这会让协程彻底挂起,无法被 asyncio.cancel() 中断
HTTP/2 支持需要手动启用且有兼容陷阱
httpx 默认用 HTTP/1.1,即使服务器支持 HTTP/2,也不会自动升版。想用 HTTP/2 得主动开开关,但开了之后,有些老中间件(比如某些版本的 Envoy、Nginx 1.19 之前)会直接 reset 连接,报错 RemoteProtocolError: malformed HTTP/2 message。
- 启用方式是
httpx.AsyncClient(http2=True),但仅当确认上下游全链路支持 HTTP/2 时才开 - 开启后必须配
limits:HTTP/2 复用连接更强,但单连接并发上限受服务端限制,建议加limits=httpx.Limits(max_connections=100, max_keepalive_connections=20) - 调试时加
httpx.HTTPTransport(verify=False, http2=True, retries=0)可绕过 TLS 验证和重试干扰,快速定位是否是协议层问题
与 requests 混用时的 session 复用误区
有人想把 requests.Session 和 httpx.AsyncClient 当成等价物混用,比如缓存 token 后用 requests 发同步请求、用 httpx 发异步请求——这会导致 cookie、auth header、proxy 等状态完全不共享,甚至同一域名下 TCP 连接池也互不感知。
立即学习“Python免费学习笔记(深入)”;
- 不要跨库复用认证逻辑:token 获取后,统一用
headers={"Authorization": f"Bearer {token}"}显式传给每个client.get() - 代理配置要分别设置:
proxies={"https://": "http://127.0.0.1:8080"}对 httpx 有效,对 requests 无效;反之亦然 - 如果必须共用底层连接(极少见),改用
httpx.AsyncClient(transport=httpx.AsyncHTTPTransport(...))自定义 transport,而不是试图桥接 requests
最常被忽略的其实是 DNS 缓存:httpx 默认复用 anyio 的 resolver,不刷新 TTL,长时间运行的服务可能连到已下线的 IP。真要稳,得自己套一层带 TTL 的 DNS cache,或者定期重建 client。










