HTTPX
httpx menggantikan requests: API yang hampir identik, tapi mendukung async, HTTP/2, dan timeout yang benar.
Intisari
- API-nya hampir sama dengan
requests, jadi pengetahuanmu terbawa langsung. - Selalu pakai objek
Client, bukan fungsi tingkat modul โ koneksi jadi dipakai ulang. - Timeout default httpx adalah 5 detik. Untuk panggilan LLM, ini terlalu pendek โ atur eksplisit.
raise_for_status()harus dipanggil sendiri; httpx tidak melempar otomatis pada 4xx/5xx.- Satu library, dua mode:
Clientuntuk sinkron,AsyncClientuntuk async.
Kenapa httpx, bukan requests
requests | httpx | |
|---|---|---|
| Dukungan async | โ Tidak ada | โ
AsyncClient |
| HTTP/2 | โ | โ (opsional) |
| Timeout default | Tidak ada โ menunggu selamanya | 5 detik |
| Type hint | Minim | Lengkap |
| Streaming | Terbatas | Sync & async |
| Status | Perawatan minimal | Aktif |
Dasar
uv add httpx
import httpx
r = httpx.get("https://api.contoh.com/data")
r.status_code # 200
r.json() # parse JSON
r.text # sebagai string
r.content # sebagai bytes
r.headers["content-type"]
r.elapsed # timedelta โ durasi request
Ini bentuk untuk mencoba di REPL saja. Setiap panggilan tingkat modul membuat koneksi
TCP dan handshake TLS baru, lalu membuangnya. Untuk kode sungguhan, pakai Client.
Client โ cara yang benar
with httpx.Client(
base_url="https://api.contoh.com",
headers={"Authorization": f"Bearer {token}"},
timeout=30.0,
) as client:
r1 = client.get("/dokumen") # koneksi dibuat
r2 = client.get("/pengguna") # koneksi yang SAMA dipakai lagi
r3 = client.post("/cari", json={"q": "python"})
Kenapa ini penting: handshake TLS makan 100โ300 ms. Kalau kamu memanggil API yang sama seratus kali dengan client baru tiap kali, kamu membuang 10โ30 detik hanya untuk berjabat tangan berulang-ulang. Connection pooling menghapus biaya itu.
Timeout
httpx.Client(timeout=30.0) # 30 detik untuk semua tahap
httpx.Client(timeout=None) # tanpa batas โ jangan dipakai di production
# Kendali per tahap
httpx.Client(timeout=httpx.Timeout(
connect=5.0, # membuka koneksi
read=60.0, # menunggu byte berikutnya โ ini yang penting untuk LLM
write=10.0, # mengirim body
pool=5.0, # menunggu slot koneksi dari pool
))
# Override untuk satu request
client.get("/lambat", timeout=120.0)
Untuk streaming LLM, yang penting adalah read. Ia mengukur jeda
antar potongan data, bukan total durasi. Jadi read=60 berarti
"gagalkan kalau tidak ada byte baru selama 60 detik" โ respons yang berjalan 10 menit
tetap aman selama datanya terus mengalir.
Menangani error
try:
r = client.get("/data")
r.raise_for_status() # โ WAJIB โ httpx tidak melempar otomatis
data = r.json()
except httpx.HTTPStatusError as e:
print(f"status {e.response.status_code}: {e.response.text}")
except httpx.TimeoutException:
print("timeout")
except httpx.ConnectError:
print("tidak bisa terhubung")
except httpx.HTTPError as e:
print(f"error http: {e}")
| Exception | Kapan | Layak diulang? |
|---|---|---|
ConnectError | DNS gagal, koneksi ditolak | Ya |
ConnectTimeout | Gagal membuka koneksi | Ya |
ReadTimeout | Server terlalu lama membalas | Ya |
HTTPStatusError | Status 4xx / 5xx | Hanya 429 dan 5xx |
RequestError | Induk dari semua error jaringan | โ |
Mengirim data
client.post("/api", json={"nama": "Budi"}) # JSON
client.post("/form", data={"nama": "Budi"}) # form-encoded
client.post("/upload", files={"berkas": open("a.pdf", "rb")})
client.get("/cari", params={"q": "python", "limit": 10})
Streaming
with client.stream("GET", "/file-besar") as r:
r.raise_for_status()
for potongan in r.iter_bytes():
tulis(potongan)
# Server-Sent Events โ bentuk yang dipakai LLM streaming
with client.stream("POST", "/chat", json=payload) as r:
for baris in r.iter_lines():
if baris.startswith("data: "):
proses(json.loads(baris[6:]))
Retry ringan bawaan
transport = httpx.HTTPTransport(retries=3) # HANYA untuk error koneksi
client = httpx.Client(transport=transport)
Ini tidak mengulang status 429 atau 5xx โ hanya kegagalan koneksi tingkat rendah.
Untuk retry yang benar-benar berguna (dengan exponential backoff dan pemilihan status),
pakai tenacity โ materi berikutnya.
Fitur lain yang berguna
client = httpx.Client(http2=True) # butuh: uv add "httpx[http2]"
client = httpx.Client(follow_redirects=True) # httpx TIDAK mengikuti redirect secara default
client = httpx.Client(proxy="http://proxy:8080")
client = httpx.Client(limits=httpx.Limits(max_connections=100, max_keepalive_connections=20))
Testing dengan mock transport
def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(200, json={"hasil": "palsu"})
client = httpx.Client(transport=httpx.MockTransport(handler))
r = client.get("https://api.contoh.com/apa-saja")
r.json() # {'hasil': 'palsu'} โ tidak ada jaringan yang tersentuh
Ini yang akan kamu pakai di Fase 6 untuk menguji kode tanpa memanggil API asli.
Perbedaan kecil dari requests
| Perilaku | requests | httpx |
|---|---|---|
| Redirect | Diikuti otomatis | Tidak โ set follow_redirects=True |
| Timeout default | Tak terbatas | 5 detik |
| Nama parameter | proxies= | proxy= |
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.