requests sudah jadi pilihan default untuk urusan HTTP di Python selama bertahun-tahun. Tapi begitu kamu butuh memanggil ratusan endpoint sekaligus tanpa membuat koneksi baru setiap kali, atau server tujuanmu menuntut HTTP/2, requests mulai terasa sempit. httpx lahir untuk menutup lubang itu. API-nya nyaris identik dengan requests, jadi migrasinya gampang, tapi dia menambahkan dukungan async, HTTP/2, dan kontrol timeout yang lebih rapi.
Kenapa httpx, bukan requests?
Kalau kamu cuma butuh request sinkron sederhana, requests masih oke. Perbedaannya muncul saat aplikasimu harus menangani banyak request I/O sekaligus. requests itu blocking, artinya tiap request menahan thread sampai respons balik. Di aplikasi async — misalnya bot atau service yang juga jalan di atas asyncio — kamu tidak bisa begitu saja pakai requests tanpa menyumbat event loop. httpx menyediakan AsyncClient yang bisa di-await, plus HTTP/2 lewat satu flag.
Instalasi
Pasang paket dasarnya dengan pip. Kalau kamu butuh HTTP/2, tambahkan ekstra http2 karena itu menarik dependency h2.
pip install httpx
# untuk dukungan HTTP/2
pip install "httpx[http2]"
Request GET pertama
Bentuk paling sederhana persis seperti requests. httpx menyediakan fungsi top-level httpx.get, httpx.post, dan seterusnya untuk skrip sekali jalan.
import httpx
r = httpx.get("https://httpbin.org/get")
print(r.status_code) # 200
print(r.json()["url"]) # https://httpbin.org/get
httpbin.org adalah service publik untuk latihan request HTTP — dia memantulkan kembali apa pun yang kamu kirim. Aman dipakai untuk semua contoh di artikel ini.
Query parameter, header, dan response
Untuk request berulang, gunakan httpx.Client sebagai context manager. Client memakai connection pooling, jadi koneksi ke host yang sama dipakai ulang alih-alih dibuka dari nol setiap request. Ini yang bikin httpx jauh lebih cepat saat memanggil banyak endpoint.
import httpx
with httpx.Client(base_url="https://httpbin.org") as client:
r = client.get("/get", params={"page": 2, "limit": 10})
print(r.url) # https://httpbin.org/get?page=2&limit=10
print(r.status_code) # 200
print(r.json()["args"]) # {'page': '2', 'limit': '10'}
print(r.headers["content-type"])
base_url menyingkat semua path. Header dikirim lewat argumen headers, dan responsnya punya r.text (string), r.content (bytes), serta r.json() untuk parse JSON otomatis.
Mengirim data: JSON dan form
with httpx.Client(base_url="https://httpbin.org") as client:
# JSON body
r = client.post("/post", json={"nama": "Budi", "umur": 25})
print(r.json()["json"]) # {'nama': 'Budi', 'umur': 25}
# Form-encoded
r = client.post("/post", data={"username": "budi", "password": "rahasia"})
print(r.json()["form"]) # {'username': 'budi', 'password': 'rahasia'}
# Upload file
with open("data.csv", "rb") as f:
r = client.post("/post", files={"file": f})
print(r.status_code)
Pakai json= untuk mengirim body JSON — httpx otomatis menyetel Content-Type: application/json. Untuk form standar HTML gunakan data=, dan untuk upload file gunakan files=. Saat server membalas error 4xx/5xx, panggil r.raise_for_status() supaya kegagalan itu tidak lolos diam-diam.
Timeout dan retry
import httpx
# default timeout httpx adalah 5 detik
r = httpx.get("https://httpbin.org/delay/3", timeout=10.0)
print(r.status_code)
# kontrol timeout per fase: connect, read, write, pool
timeout = httpx.Timeout(10.0, connect=5.0)
r = httpx.get("https://httpbin.org/delay/3", timeout=timeout)
# retry otomatis untuk kegagalan koneksi (maks 3x percobaan ulang)
transport = httpx.HTTPTransport(retries=3)
with httpx.Client(transport=transport) as client:
r = client.get("https://httpbin.org/anything")
print(r.status_code)
retries di HTTPTransport hanya mengulang kegagalan level koneksi — server mati, timeout connect, DNS gagal. Untuk mengulang respons 500 atau 503 kamu perlu logika sendiri, misalnya loop dengan r.raise_for_status() di dalam try/except.
Async dengan AsyncClient
Di sinilah httpx benar-benar unggul. AsyncClient punya API yang sama, bedanya semua method bisa di-await. Ini penting kalau kode kamu sudah jalan di atas asyncio — misalnya di bot, atau saat memanggil REST API FastAPI milikmu sendiri dari service lain.
import asyncio
import httpx
async def main():
async with httpx.AsyncClient() as client:
r = await client.get("https://httpbin.org/get")
print(r.status_code)
print(r.json()["url"])
asyncio.run(main())
Concurrency: puluhan request dalam hitungan detik
Karena tiap await membebaskan event loop, kamu bisa menjalankan banyak request sekaligus dengan asyncio.gather. Contoh di bawah memanggil empat endpoint yang masing-masing delay 1 detik, tapi total waktunya cuma sekitar 1 detik — bukan 4.
import asyncio
import time
import httpx
urls = [f"https://httpbin.org/delay/{i}" for i in (1, 1, 1, 1)]
async def fetch(client, url):
r = await client.get(url)
return r.status_code
async def main():
async with httpx.AsyncClient(timeout=10.0) as client:
start = time.perf_counter()
hasil = await asyncio.gather(*(fetch(client, u) for u in urls))
print(hasil) # [200, 200, 200, 200]
print(f"selesai dalam {time.perf_counter() - start:.2f} detik")
asyncio.run(main())
HTTP/2
HTTP/2 memungkinkan banyak request lewat satu koneksi TCP dan mengurangi overhead header. Di httpx tinggal nyalakan satu flag — asalkan server tujuan mendukungnya.
import httpx
client = httpx.Client(http2=True)
r = client.get("https://http2.golang.org/")
print(r.http_version) # HTTP/2
client.close()
Pakai http2=True hanya kalau kamu benar-benar butuh. Untuk API biasa, HTTP/1.1 dengan connection pooling sudah cukup cepat dan lebih ringan dari sisi dependensi.
Streaming download file besar
Jangan panggil client.get untuk file ratusan megabyte — itu memuat seluruh respons ke memori sekaligus. Gunakan httpx.stream untuk membaca respons per potongan.
import httpx
total = 0
with httpx.stream("GET", "https://speed.hetzner.de/100MB.bin") as r:
for chunk in r.iter_bytes():
total += len(chunk)
print(f"{total} bytes diterima")
Testing tanpa server: MockTransport
Kalau kamu menulis unit test untuk kode yang memanggil API eksternal, jangan biarkan test-mu bergantung ke internet. httpx punya MockTransport yang mengembalikan respons buatan tanpa melakukan request sungguhan — cocok dipadukan dengan pytest.
import httpx
def handler(request):
return httpx.Response(200, json={"status": "ok", "path": request.url.path})
client = httpx.Client(transport=httpx.MockTransport(handler))
r = client.get("https://contoh.test/api/users")
print(r.json()) # {'status': 'ok', 'path': '/api/users'}
Mulai dari yang kecil: ambil satu skrip kamu yang sekarang pakai requests, ganti impor dan method call-nya ke httpx, lalu jalankan. Kalau ada bagian yang butuh banyak request sekaligus, pindahkan ke AsyncClient dan asyncio.gather. Dari situ kamu akan lihat sendiri di mana httpx menyelamatkan waktu.