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.

bash
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.

python
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

python
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

python
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)
python
# 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.

python
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.

python
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.

python
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.

python
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.

python
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.