A high-performance async HTTP/1.1 and HTTP/2 client for Python
Project description
HyperHTTP
A fast, correct async HTTP client for Python. HTTP/1.1 and HTTP/2, built on asyncio.
HyperHTTP is designed for services that make a lot of outbound HTTP calls — API gateways, crawlers, load generators, backend-to-backend traffic — where throughput, tail latency, and memory behaviour under concurrency all matter.
Features
- HTTP/1.1 and HTTP/2. HTTP/2 is negotiated via ALPN automatically. Multiple concurrent requests to the same host multiplex as streams over a single TCP connection.
- Strict, smuggling-resistant parser. Rejects
Content-Length+Transfer-Encodingconflicts and differing duplicateContent-Lengthheaders. - Connection pool with global and per-host caps, FIFO waiter fairness, and connection recycling.
- Transparent decoding of
gzipanddeflateout of the box;brandzstdwhen the optional libraries are installed. - DNS cache with Happy Eyeballs v2 — IPv6/IPv4 races with a 250 ms stagger, bounded-TTL cache.
- Retry and circuit breaker with error classification, decorrelated-jitter backoff, and
Retry-Aftersupport. The first failure surfaces the original typed exception;RetryErroronly appears after at least one retry has run. - Cookies, redirects, streaming bodies, JSON via
orjsonwhen available.
Installation
pip install hyperhttp
# Optional fast extras: uvloop, orjson, brotli, zstandard, h11
pip install 'hyperhttp[speed]'
Quick start
import asyncio
import hyperhttp
async def main():
async with hyperhttp.Client() as client:
response = await client.get("https://example.com")
await response.aread()
print(response.status_code, response.text[:120])
response = await client.post(
"https://httpbin.org/post",
json={"key": "value"},
)
await response.aread()
print(response.json())
asyncio.run(main())
Streaming responses
async with hyperhttp.Client() as client:
response = await client.get("https://example.com/large.bin")
async for chunk in response.aiter_bytes():
handle(chunk)
Parallel requests
async with hyperhttp.Client() as client:
urls = [
"https://httpbin.org/get",
"https://httpbin.org/ip",
"https://httpbin.org/headers",
]
responses = await asyncio.gather(*(client.get(u) for u in urls))
for r in responses:
await r.aread()
Retry and circuit breaker
from hyperhttp import Client
from hyperhttp.errors.retry import RetryPolicy
from hyperhttp.utils.backoff import DecorrelatedJitterBackoff
retry_policy = RetryPolicy(
max_retries=5,
retry_categories=["TRANSIENT", "TIMEOUT", "SERVER"],
status_force_list=[429, 500, 502, 503, 504],
backoff_strategy=DecorrelatedJitterBackoff(base=0.1, max_backoff=10.0),
respect_retry_after=True,
)
async with Client(retry=retry_policy) as client:
response = await client.get("https://api.example.com/things")
The first attempt is never wrapped in RetryError — callers always see the typed transport or timeout exception on the initial failure. RetryError is only raised once at least one retry has been attempted.
Connection pooling
client = Client(
max_connections=200, # global cap across all hosts
max_keepalive_connections=32, # per host
http2=True, # negotiate HTTP/2 via ALPN
)
HTTP/2
HTTP/2 is negotiated during the TLS handshake. When a host speaks h2, concurrent requests to that host share a single TCP connection and multiplex as streams, bounded by the server-advertised MAX_CONCURRENT_STREAMS. Pass http2=False to force HTTP/1.1.
uvloop
import hyperhttp
hyperhttp.install_uvloop() # no-op if uvloop isn't installed
Benchmarks
Measured against aiohttp and httpx on a local aiohttp loopback server (no network, no DNS, no TLS), 2 000 requests per (client, body size), concurrency 64, Python 3.12, macOS arm64.
| Body size | Client | RPS | P50 (ms) | P95 (ms) | P99 (ms) |
|---|---|---|---|---|---|
| 200 B | hyperhttp | 3 699 | 11.1 | 12.7 | 31.3 |
| 200 B | aiohttp | 3 904 | 11.5 | 14.7 | 28.5 |
| 200 B | httpx | 199 | 210.4 | 948.9 | 1452.9 |
| 10 KiB | hyperhttp | 3 527 | 11.8 | 13.7 | 32.2 |
| 10 KiB | aiohttp | 3 794 | 11.9 | 15.1 | 29.1 |
| 10 KiB | httpx | 203 | 204.8 | 902.1 | 1413.2 |
| 1 MiB | hyperhttp | 1 303 | 42.7 | 46.5 | 71.2 |
| 1 MiB | aiohttp | 1 317 | 43.0 | 47.6 | 61.5 |
| 1 MiB | httpx | 98 | 345.1 | 1465.9 | 2784.2 |
HyperHTTP runs within ~5% of aiohttp across every body size and is 15–20× faster than httpx on this workload. Numbers come from loopback, so they reflect client-side CPU cost; real networks flatten the differences.
Run it yourself:
pip install 'hyperhttp[bench]'
python examples/benchmark_local.py
Documentation
Full documentation: hyperhttp.readthedocs.io
Contributing
Issues and pull requests are welcome. See the Contributing Guide.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hyperhttp-2.0.0.tar.gz.
File metadata
- Download URL: hyperhttp-2.0.0.tar.gz
- Upload date:
- Size: 54.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ba22d12c0c3bd0e45f35360eec589c0bd2e5f15af7dd33f1772e9f950bbff36f
|
|
| MD5 |
82c44bc8899c8d3d3922b1c493c9c91b
|
|
| BLAKE2b-256 |
deda2d35c9043db3e282e2abd3c59a13bea1ad112295d9dc8b651dcb8f5fac19
|
File details
Details for the file hyperhttp-2.0.0-py3-none-any.whl.
File metadata
- Download URL: hyperhttp-2.0.0-py3-none-any.whl
- Upload date:
- Size: 62.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
62cbecc1ffe508b1711d732c746a1832b2de03b59073a3297bf51aea8c16074f
|
|
| MD5 |
5afa3644e553f2b6fb2efc3d72238462
|
|
| BLAKE2b-256 |
5fce9f168097577eb944029bbb9ad2c0f85214dc0b2aa1eee19aa316a61c981e
|