Menulis klien HTTP dengan urllib terasa seperti mengisi formulir panjang untuk pekerjaan singkat. Dua library modern menghapus kejenuhan tersebut: requests yang menjadi standar de facto selama lebih dari satu dekade, dan httpx generasi penerusnya dengan dukungan async. Pada Chapter ini kita memakai keduanya terhadap server lokal yang sama, mempelajari pola pemakaian yang menyusun 90% kebutuhan nyata: mengirim parameter, body JSON, header, timeout, dan session.
Server API Lokal #
Simpan kode berikut sebagai server_api.py dan jalankan dengan python3 server_api.py — server ini melayani data produk dalam format JSON:
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json
class Handler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def do_GET(self):
produk = [
{"id": 1, "nama": "Kopi Gayo", "harga": 85000},
{"id": 2, "nama": "Teh Melati", "harga": 45000},
]
self._kirim_json(200, {"produk": produk})
def do_POST(self):
panjang = int(self.headers["Content-Length"])
data = json.loads(self.rfile.read(panjang))
self._kirim_json(201, {"pesan": "produk diterima", "data": data})
def _kirim_json(self, status, payload):
body = json.dumps(payload).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, format, *args):
pass
ThreadingHTTPServer(("127.0.0.1", 8123), Handler).serve_forever()requests, Klien yang Paling Populer #
Satu function requests.get() menutup seluruh siklus HTTP pada Chapter HTTP Request dan Response. Semua opsi penting dilewatkan sebagai keyword argument:
import requests
respons = requests.get(
"http://127.0.0.1:8123/",
params={"kategori": "minuman"},
headers={"X-Klien": "belajar"},
timeout=3,
)
print("Status :", respons.status_code)
print("Tipe :", respons.headers["Content-Type"])
print("Produk :", [p["nama"] for p in respons.json()["produk"]])
respons = requests.post(
"http://127.0.0.1:8123/",
json={"nama": "Gula Aren", "harga": 32000},
timeout=3,
)
print("Status :", respons.status_code)
print("Balasan:", respons.json()["pesan"])Output yang Dihasilkan:
Status : 200
Tipe : application/json
Produk : ['Kopi Gayo', 'Teh Melati']
Status : 201
Balasan: produk diterimaMari kita telaah bagian-bagian kuncinya:
- Baris 5 (
params=...): Library mengubah dictionary menjadi query string (?kategori=minuman) dan mengurus escaping otomatis — tidak perluurlencode()manual. - Baris 6 (
headers=...): Header khusus digabungkan ke request; header umum sepertiContent-Typediurus sendiri oleh library. - Baris 7 (
timeout=3): Batas waktu dalam detik; tanpa parameter ini,requestsmenunggu tanpa batas — kita bahas tuntas pada Chapter Timeouts dan Retries. - Baris 15 (
json=...): Dictionary di-serialize menjadi body JSON sekaligus menyetel headerContent-Type: application/json. - Baris 11 dan 19 (
.json()): Method.json()membaca body dan mem-parse JSON menjadi structure Python —dictdanlist— tanpa langkah.read().decode()seperti padaurllib.
Object response membawa seluruh informasi yang kita perlukan: .status_code (int), .headers (dictionary-like), .text (body sebagai str), dan .json() (body ter-parse).
Session untuk Menggunakan Ulang Koneksi #
Membuka koneksi TCP baru untuk setiap permintaan itu boros — seperti menelepon ulang setiap kali ingin menambah satu kalimat. Class Session menjaga koneksi tetap hidup antar permintaan, sekaligus menyimpan header yang sama:
import requests
with requests.Session() as sesi:
sesi.headers.update({"X-Klien": "belajar"})
for nomor in range(1, 4):
respons = sesi.get("http://127.0.0.1:8123/", timeout=3)
print(f"Panggilan {nomor}: status {respons.status_code}")Output yang Dihasilkan:
Panggilan 1: status 200
Panggilan 2: status 200
Panggilan 3: status 200Ketiga panggilan tersebut berjalan pada satu koneksi TCP yang sama berkat header Connection: keep-alive yang dikelola Session. Untuk tiga panggilan penghematannya kecil; untuk ratusan panggilan dalam proses ETL harian, selisihnya nyata. Pemakaian Session juga satu-satunya cara menyimpan konfigurasi bersama — misalnya header Authorization untuk seluruh permintaan — tanpa menyalinnya berulang kali di setiap pemanggilan.
httpx, API Serupa dengan Fitur Modern #
httpx sengaja meniru API requests hampir persis, sehingga kode bisa dipindahkan dengan perubahan minimal — dan menambahkan satu hal yang tidak dimiliki requests: client async bawaan.
import httpx
with httpx.Client(base_url="http://127.0.0.1:8123", timeout=3) as klien:
respons = klien.get("/")
print("Status :", respons.status_code)
print("Produk :", [p["nama"] for p in respons.json()["produk"]])Output yang Dihasilkan:
Status : 200
Produk : ['Kopi Gayo', 'Teh Melati']Versi async-nya memakai AsyncClient bersama asyncio.gather(), sehingga tiga permintaan berjalan serentak:
import asyncio
import httpx
async def ambil_semua():
async with httpx.AsyncClient(
base_url="http://127.0.0.1:8123", timeout=3
) as klien:
tugas = [klien.get("/") for _ in range(3)]
return await asyncio.gather(*tugas)
hasil = asyncio.run(ambil_semua())
print("Jumlah respons:", len(hasil))
for respons in hasil:
print("Status:", respons.status_code, "-", len(respons.json()["produk"]), "produk")Output yang Dihasilkan:
Jumlah respons: 3
Status: 200 - 2 produk
Status: 200 - 2 produk
Status: 200 - 2 produkFunction asyncio.run() menjalankan coroutine sampai selesai; gather() mengumpulkan ketiga coroutine dan mengembalikan hasilnya sesuai urutan pembuatan. Materi mendalam tentang pola async/await tersedia pada Section Asynchronous Programming.
requests atau httpx #
| Aspek | requests | httpx |
|---|---|---|
API dasar (get, json=, params=) | Ya | Identik |
| Async | Tidak | Ya, AsyncClient |
| HTTP/2 | Tidak | Opsional lewat http2=True |
| Status ekosistem | Paling matang, dipakai di mana-mana | Modern dan aktif berkembang |
Rekomendasi praktisnya: pilih requests untuk kodebase yang sudah ada dan tutorial yang banyak menggunakannya; pilih httpx untuk proyek baru yang membutuhkan async atau HTTP/2. Karena API keduanya nyaris sama, keahlian yang kita bangun di sini berlaku untuk keduanya. Apapun pilihannya, biasakan tiga hal pada setiap pemanggilan: parameter timeout selalu terpasang, method .json() dipakai alih-alih mem-parse .text sendiri, dan status code diperiksa sebelum data dipakai.