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:

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

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

text
Status : 200
Tipe   : application/json
Produk : ['Kopi Gayo', 'Teh Melati']
Status : 201
Balasan: produk diterima

Mari kita telaah bagian-bagian kuncinya:

  1. Baris 5 (params=...): Library mengubah dictionary menjadi query string (?kategori=minuman) dan mengurus escaping otomatis — tidak perlu urlencode() manual.
  2. Baris 6 (headers=...): Header khusus digabungkan ke request; header umum seperti Content-Type diurus sendiri oleh library.
  3. Baris 7 (timeout=3): Batas waktu dalam detik; tanpa parameter ini, requests menunggu tanpa batas — kita bahas tuntas pada Chapter Timeouts dan Retries.
  4. Baris 15 (json=...): Dictionary di-serialize menjadi body JSON sekaligus menyetel header Content-Type: application/json.
  5. Baris 11 dan 19 (.json()): Method .json() membaca body dan mem-parse JSON menjadi structure Python — dict dan list — tanpa langkah .read().decode() seperti pada urllib.

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:

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

text
Panggilan 1: status 200
Panggilan 2: status 200
Panggilan 3: status 200

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

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

text
Status : 200
Produk : ['Kopi Gayo', 'Teh Melati']

Versi async-nya memakai AsyncClient bersama asyncio.gather(), sehingga tiga permintaan berjalan serentak:

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

text
Jumlah respons: 3
Status: 200 - 2 produk
Status: 200 - 2 produk
Status: 200 - 2 produk

Function 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 #

Aspekrequestshttpx
API dasar (get, json=, params=)YaIdentik
AsyncTidakYa, AsyncClient
HTTP/2TidakOpsional lewat http2=True
Status ekosistemPaling matang, dipakai di mana-manaModern 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.