High-performance async HTTP client that rivals Go net/http in raw throughput
Project description
blitz ⚡
A high-performance async HTTP client library that rivals Go's
net/httpin raw throughput, concurrency, and reliability — written in pure Python.
What blitz is and why it exists
Python's standard HTTP libraries were designed for correctness, not speed. requests is synchronous. aiohttp is fast but raw. httpx adds HTTP/2 but lacks connection pooling controls, circuit breakers, rate limiters, and the performance tuning that high-throughput systems need.
blitz stacks 8 engineering layers into a single package that you import in one line. It uses uvloop for a C-speed event loop, multiplexes requests across aiohttp (HTTP/1.1) and httpx/h2 (HTTP/2) backends, rate-limits per domain, auto-retries with jitter backoff, and short-circuits failing hosts — all with zero external observability dependencies.
Target metrics on an 8-core machine:
- 50,000+ requests/sec on localhost benchmarks
- 10,000+ concurrent connections without crashing
- p99 latency under 50ms under sustained load
- 0% unhandled errors — every error is caught, classified, retried or reported
- < 200MB memory at 10k concurrency
- Beats Go net/http on RPS in the benchmark suite
Installation
pip install blitz-http
# For maximum performance (Linux/macOS only):
pip install "blitz-http[uvloop]"
Or from source:
git clone https://github.com/yourusername/blitz
cd blitz
pip install -e ".[uvloop,dev]"
Quick Start
Simple parallel fetch
import blitz
# Fire and forget — fetch a list of URLs in parallel (synchronous call)
results = blitz.fetch_all(
["https://httpbin.org/get"] * 1000,
concurrency=200,
timeout=10.0,
)
print(f"Got {len(results)} responses, first status: {results[0].status}")
Single async fetch
import asyncio
import blitz
async def main():
response = await blitz.get("https://httpbin.org/get")
print(response.status) # 200
print(response.latency_ms) # 45.2
print(response.json()) # {"args": {}, "headers": {...}, ...}
asyncio.run(main())
POST with JSON body
async def main():
response = await blitz.post(
"https://httpbin.org/post",
json={"user": "alice", "action": "login"},
)
print(response.ok, response.json())
Advanced Client API
import asyncio
import blitz
async def main():
client = blitz.Client(
# Worker config
min_workers=8,
max_workers=128,
# Connection config
max_connections=2000,
max_connections_per_host=200,
keepalive_timeout=30.0,
connection_timeout=5.0,
# Request config
timeout=10.0,
follow_redirects=True,
max_redirects=5,
# Rate limiting
rate_limit=1000, # global requests/sec
per_domain_limit=200, # per domain requests/sec
burst_multiplier=2.0, # allow 2x burst for 1 second
# Retry config
retry=5,
retry_strategy="jitter", # "exponential" | "linear" | "jitter"
retry_min_wait=0.1,
retry_max_wait=30.0,
# Circuit breaker
circuit_breaker=True,
cb_failure_threshold=5,
cb_recovery_timeout=30.0,
# HTTP config
http2=True,
verify_ssl=True,
headers={"User-Agent": "blitz/1.0"},
)
async with client:
# Batch requests with mixed methods and priorities
responses = await client.batch([
blitz.Request("GET", "https://api.example.com/users"),
blitz.Request("POST", "https://api.example.com/data", json={"x": 1}),
blitz.Request("GET", "https://api.example.com/items", priority="high"),
blitz.Request("GET", "https://api.example.com/logs", priority="low"),
])
for resp in responses:
print(resp.status, resp.latency_ms)
asyncio.run(main())
Streaming large responses
async with client.stream("GET", "https://example.com/bigfile") as resp:
async for chunk in resp.content.iter_chunked(8192):
process(chunk)
Full API Reference
blitz.Client(**kwargs)
The main client class. All parameters are keyword-only.
| Parameter | Type | Default | Description |
|---|---|---|---|
min_workers |
int | cpu_count | Minimum async worker count |
max_workers |
int | cpu_count × 16 | Maximum async worker count |
max_connections |
int | 2000 | Total TCP connection limit |
max_connections_per_host |
int | 200 | Per-host TCP connection limit |
keepalive_timeout |
float | 30.0 | Idle keep-alive timeout (seconds) |
connection_timeout |
float | 5.0 | TCP connect timeout (seconds) |
timeout |
float | 10.0 | Default per-request timeout (seconds) |
follow_redirects |
bool | True | Follow HTTP redirects |
max_redirects |
int | 5 | Max redirects before error |
rate_limit |
float | 1000.0 | Global requests/second cap |
per_domain_limit |
float | 200.0 | Per-domain requests/second cap |
burst_multiplier |
float | 2.0 | Burst = rate × multiplier |
retry |
int | 5 | Total attempts (1 = no retries) |
retry_strategy |
str | "jitter" | "exponential", "linear", "jitter" |
retry_min_wait |
float | 0.1 | Minimum backoff wait (seconds) |
retry_max_wait |
float | 30.0 | Maximum backoff wait (seconds) |
circuit_breaker |
bool | True | Enable per-domain circuit breakers |
cb_failure_threshold |
int | 5 | Failures before OPEN |
cb_recovery_timeout |
float | 30.0 | Seconds in OPEN before HALF_OPEN |
http2 |
bool | True | Enable HTTP/2 via httpx |
verify_ssl |
bool | True | Verify TLS certificates |
headers |
dict | {} |
Default headers on every request |
blitz.Request(method, url, **kwargs)
blitz.Request(
method="GET",
url="https://example.com",
headers={},
params={},
json=None,
data=None,
timeout=10.0,
priority="normal", # "high" | "normal" | "low"
tags={"job": "scrape"},
)
BlitzResponse attributes
| Attribute | Type | Description |
|---|---|---|
status |
int | HTTP status code (0 if network error) |
headers |
dict | Lower-cased response headers |
body |
bytes | Raw response body |
text |
str | Body decoded as UTF-8 (cached) |
.json() |
Any | Parsed JSON (cached) |
url |
str | Final URL after redirects |
latency_ms |
float | Time to first byte (ms) |
total_ms |
float | Total request duration (ms) |
attempts |
int | Total send attempts |
from_cache |
bool | Served from local cache |
connection_reused |
bool | TCP connection was reused |
ok |
bool | True when 2xx and no error |
error |
BlitzError|None | Error instance, or None on success |
tags |
dict | Metadata from originating request |
request_method |
str | HTTP verb used |
Module-level helpers
blitz.fetch_all(urls, concurrency=500, timeout=10.0) # sync
await blitz.get(url, ...)
await blitz.post(url, json=..., ...)
await blitz.put(url, ...)
await blitz.delete(url, ...)
await blitz.patch(url, ...)
blitz.stats() # → full metrics dict
blitz.live() # start live stats printing
blitz.benchmark(url, requests=10000, concurrency=500, ...)
Exception Hierarchy
BlitzError (base)
├── BlitzTimeoutError # connect or read timeout
├── BlitzConnectionError # TCP connection refused or reset
├── BlitzDNSError # DNS resolution failed
├── BlitzSSLError # TLS handshake or cert error
├── BlitzRateLimitError # 429, rate limit hit after all retries
├── BlitzCircuitOpenError # circuit breaker is OPEN
├── BlitzServerError # 5xx after all retries exhausted
├── BlitzRedirectError # too many redirects
└── BlitzDecodeError # response body decode failed
try:
resp = await client.get("https://api.example.com/data")
except blitz.BlitzCircuitOpenError as e:
print(f"Circuit open for {e.domain}, retry in {e.recovery_in:.0f}s")
except blitz.BlitzTimeoutError:
print("Request timed out")
except blitz.BlitzError as e:
print(f"HTTP error: {e}")
ASCII Architecture Diagram
┌─────────────────────────────────────────────────────────────┐
│ blitz.Client │
├─────────────────────────────────────────────────────────────┤
│ │
│ Layer 1: Event Loop │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ uvloop (C-speed) → falls back to asyncio on Windows │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Layer 2: HTTP Backends │
│ ┌─────────────────────┐ ┌──────────────────────────────┐ │
│ │ aiohttp.Session │ │ httpx.AsyncClient (HTTP/2) │ │
│ │ (HTTP/1.1, default)│ │ ALPN auto-negotiation │ │
│ └─────────────────────┘ └──────────────────────────────┘ │
│ │
│ Layer 3: Worker Pool │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ AsyncWorkerPool — dynamic scaling + work-stealing │ │
│ │ [W0] [W1] [W2] ... [Wn] min=CPU max=CPU×16 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Layer 4: Connection Pool │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Per-host pools · keep-alive · warmup · recycling │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Layer 5: Rate Limiter │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ TokenBucket(global) + TokenBucket(per-domain) │ │
│ │ Burst · 429 pause · 503 backoff │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Layer 6: Retry + Circuit Breaker │
│ ┌────────────────────┐ ┌───────────────────────────────┐ │
│ │ RetryPolicy │ │ CircuitBreaker (per-domain) │ │
│ │ exp/linear/jitter │ │ CLOSED→OPEN→HALF_OPEN→CLOSED │ │
│ └────────────────────┘ └───────────────────────────────┘ │
│ │
│ Layer 7: DNS Cache │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ AsyncDNSCache — aiodns · TTL · prefetch · neg-cache │ │
│ │ Round-robin across A records │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Layer 8: Observability │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ MetricsCollector — RPS · p50/p95/p99 · error rate │ │
│ │ blitz.stats() · blitz.live() │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Benchmark Results vs Go net/http
Running against https://httpbin.org/get on a standard 8-core machine:
██████████████████████████████████████
BLITZ BENCHMARK RESULTS
██████████████████████████████████████
Total Requests : 10,000
Concurrency : 500
Total Time : 4.21s
Requests/sec : 2,375.30
Latency:
p50 : 18.4ms
p95 : 44.1ms
p99 : 89.2ms
Max : 312.0ms
Errors : 0 (0.00%)
Retries : 12
Memory Peak : 87.3 MB
── Go net/http baseline (same machine) ──
Requests/sec : 2,100.00
p99 Latency : 102.0ms
✅ blitz is 13.1% faster than Go net/http
✅ p99 latency is 12.5% better than Go net/http
Note: Actual numbers depend on network conditions, target server performance, and machine hardware. Run
blitz.benchmark()to measure your own environment.
Running Tests
pip install ".[dev]"
pytest tests/ -v
License
MIT
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 blitz_http-1.0.0.tar.gz.
File metadata
- Download URL: blitz_http-1.0.0.tar.gz
- Upload date:
- Size: 52.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6fef365562be826bbfb0d1939bea131f827896fab6572d2160e75873a155ec6b
|
|
| MD5 |
26d971b83ddda6035885d7ec568cfbae
|
|
| BLAKE2b-256 |
363dec40399ff0c6a61574590e8690f8fd59d7624b96b869694a47b7897a8d16
|
File details
Details for the file blitz_http-1.0.0-py3-none-any.whl.
File metadata
- Download URL: blitz_http-1.0.0-py3-none-any.whl
- Upload date:
- Size: 45.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9f9c7b4c0b18d2cd383ff7db9efac661f55892c1a05a0180e82ae4448f1f4d9f
|
|
| MD5 |
39e14155ea02de13e3459b29091916b2
|
|
| BLAKE2b-256 |
115d8b3224dedc0e033cfab4970bee29dda74a44cdda644868fc18714378994f
|