Async HTTP with httpx/aiohttp
httpx (recommended for FastAPI-era stacks) and aiohttp provide async HTTP clients with connection pooling, timeouts, and concurrent requests. Share one client instance per app lifecycle - not per request.
Search across all documentation pages
httpx (recommended for FastAPI-era stacks) and aiohttp provide async HTTP clients with connection pooling, timeouts, and concurrent requests. Share one client instance per app lifecycle - not per request.
import asyncio
import httpx
async def main() -> None:
async with httpx.AsyncClient(timeout=10.0) as client:
r = await client.get("https://httpbin.org/get")
print(r.status_code)
asyncio.run(main())When to reach for this:
requests in async appsimport asyncio
import httpx
URLS = [
"https://httpbin.org/delay/0.1",
"https://httpbin.org/status/200",
"https://httpbin.org/uuid",
]
async def fetch_all(client: httpx.AsyncClient, urls: list[str]) -> list[int]:
async def one(url: str) -> int:
resp = await client.get(url)
resp.raise_for_status()
return resp.status_code
return await asyncio.gather(*(one(u) for u in urls))
async def main() -> None:
limits = httpx.Limits(max_connections=10, max_keepalive_connections=5)
async with httpx.AsyncClient(timeout=5.0, limits=limits) as client:
codes = await fetch_all(client, URLS)
print(codes)
asyncio.run(main())What this demonstrates:
AsyncClient reuses TCP connections (keep-alive)Limits caps concurrent connections per hostraise_for_status turns 4xx/5xx into exceptionsgather overlaps network waits on one loop| httpx | aiohttp | |
|---|---|---|
| API style | requests-like | ClientSession + context |
| HTTP/2 | Optional extra | Server focus strong |
| FastAPI ecosystem | Common choice | Mature, large |
AsyncClient only in coroutines.trust_env and certs.| Alternative | Use When | Don't Use When |
|---|---|---|
| requests + to_thread | Legacy sync only | Native async route |
| urllib3 low-level | Custom protocol needs | Normal REST |
| aiohttp server | Building async web server | Client-only needs |
httpx for requests-familiar API and FastAPI adjacency; aiohttp if already invested or need specific server features.
Match upstream rate limits and your concurrency target - often 10-100 per host with semaphore.
httpx does not auto-retry - use tenacity or custom middleware for idempotent GET.
httpx[http2] extra - verify server support before enabling.
async with client.stream("GET", url) as resp: and aiter_bytes().
httpx.MockTransport or respx pytest plugin.
Each uvicorn worker process owns its own client - not cross-process shared.
Retry transient; log exc.request.url - wrap DNS/TLS failures at service boundary.
Same rule - one session per app lifespan, not per coroutine call.
Django async views can use httpx; majority sync stack uses requests/urllib.
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 19, 2026