Kode klien HTTP yang sempurna sekalipun akan bertemu jaringan yang tidak sempurna: server mati mendadak, WiFi terputus, DNS bermasalah, atau endpoint salah alamat. Tanpa strategi penanganan, satu gangguan kecil menjatuhkan seluruh program dengan traceback yang membingungkan pengguna. Chapter penutup Section ini merangkai semuanya: mengenali jenis kegagalan dari exception-nya, menangkapnya pada tingkat yang tepat, mencatatnya dengan logging, dan memutuskan kapan mencoba lagi versus gagal cepat.
Hierarki Exception pada requests #
Seluruh error yang dimunculkan library requests adalah turunan dari satu induk, requests.exceptions.RequestException. Mengenali peta keluarganya membuat kita bisa menangkap error pada tingkat presisi yang kita butuhkan:
requests.exceptions.RequestException ← induk semua error requests
├── ConnectionError ← koneksi gagal atau terputus
├── Timeout ← batas waktu terlampaui
│ ├── ConnectTimeout ← gagal saat menyambung
│ └── ReadTimeout ← server terlalu lama menjawab
└── HTTPError ← status 4xx/5xx dari raise_for_status()Aturan pembacaannya: ConnectionError berarti pesan kita tidak pernah sampai ke server, Timeout berarti sampai tetapi jawabannya tidak kembali tepat waktu, dan HTTPError berarti percakapan selesai namun server menolak — tiga situasi berbeda yang menuntut reaksi berbeda. Library httpx memiliki peta serupa dengan nama yang hampir sama: httpx.ConnectError untuk kegagalan koneksi, httpx.ReadTimeout untuk timeout baca, dan httpx.HTTPStatusError dari raise_for_status().
Menangkap yang Spesifik, Bukan yang Generik #
Mari kita lihat dua kegagalan yang paling sering terjadi. Yang pertama: server tidak ada sama sekali di port tujuan:
import requests
try:
requests.get("http://127.0.0.1:59999/", timeout=2)
except requests.exceptions.ConnectionError as err:
print("Tipe :", type(err).__name__)
print("Info :", err)Output yang Dihasilkan:
Tipe : ConnectionError
Info : HTTPConnectionPool(host='127.0.0.1', port=59999): Max retries exceeded with url: / (Caused by NewConnectionError("HTTPConnection(host='127.0.0.1', port=59999): Failed to establish a new connection: [Errno 111] Connection refused"))Pesan error tersebut panjang, tetapi bagian paling informatif ada di ujungnya: [Errno 111] Connection refused — sistem operasi menegaskan bahwa tidak ada yang mendengarkan di port itu. Yang kedua: server hidup tetapi halaman tidak ada. Nyalakan kembali server_lokal.py dari Chapter Menggunakan urllib, lalu:
import requests
respons = requests.get("http://127.0.0.1:8123/tidak-ada", timeout=3)
try:
respons.raise_for_status()
except requests.exceptions.HTTPError as err:
print("Tipe :", type(err).__name__)
print("Info :", err)Output yang Dihasilkan:
Tipe : HTTPError
Info : 404 Client Error: Not Found for url: http://127.0.0.1:8123/tidak-adaMethod raise_for_status() mengubah status 4xx/5xx menjadi exception; tanpa memanggilnya, status 404 hanya angka biasa di .status_code. Sebaliknya, padanan httpx untuk contoh pertama terlihat seperti ini:
import httpx
try:
httpx.get("http://127.0.0.1:59999/", timeout=2)
except httpx.ConnectError as err:
print("Tipe :", type(err).__name__)
print("Info :", err)Output yang Dihasilkan:
Tipe : ConnectError
Info : [Errno 111] Connection refusedMencatat Kegagalan dengan logging #
Penanganan terbaik tetap membuang informasi bila kegagalan tidak dicatat. Module logging memastikan jejak setiap kegagalan tersimpan dengan terstruktur:
import logging
import sys
import requests
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s %(name)s - %(message)s",
stream=sys.stdout,
)
catatan = logging.getLogger("klien-api")
def ambil_data(url):
try:
respons = requests.get(url, timeout=3)
respons.raise_for_status()
return respons.json()
except requests.exceptions.HTTPError as err:
catatan.error("Server menolak permintaan: %s", err)
except requests.exceptions.ConnectionError as err:
catatan.error("Tidak bisa terhubung ke server: %s", type(err).__name__)
return None
data = ambil_data("http://127.0.0.1:8123/tidak-ada")
print("Data diterima:", data)Output yang Dihasilkan:
ERROR klien-api - Server menolak permintaan: 404 Client Error: Not Found for url: http://127.0.0.1:8123/tidak-ada
Data diterima: NoneFunction ambil_data() mengembalikan data saat sukses dan None saat gagal; setiap cabang gagal dicatat oleh catatan.error() dengan penyebab yang berbeda. Penulisan logger, level, dan handler dibahas tuntas pada Chapter Module logging — di sini kita memakai bentuk paling sederhana yang sudah layak untuk produksi kecil.
Strategi, Kapan Mengulang dan Kapan Menyerah #
| Kondisi | Reaksi yang tepat |
|---|---|
ConnectionError pada server yang biasanya hidup | Coba lagi dengan backoff (Chapter Timeouts dan Retries) |
ReadTimeout pada endpoint yang memang lambat | Perbesar timeout atau coba lagi sekali |
HTTPError 4xx (401, 403, 404) | Jangan diulang — perbaiki permintaan |
HTTPError 429 | Tunggu sesuai Retry-After (Chapter Pagination dan Rate Limits) |
HTTPError 5xx | Boleh diulang beberapa kali |
| Kegagalan setelah N percobaan | Gagal cepat, sampaikan pesan ke pengguna |
Saat kegagalan akhirnya sampai ke pengguna, terjemahkan menjadi kalimat yang bisa ditindaklanjuti: “Server sedang tidak dapat dihubungi, coba beberapa saat lagi” — bukan traceback mentah, dan bukan juga None yang diam-diam. Pola lengkapnya: permintaan dengan timeout, penangkapan exception spesifik, pencatatan dengan logging, retry untuk kegagalan sementara, dan pesan yang jujur ketika semuanya gagal. Dengan empat chapter terakhir sebagai fondasi, kita siap membangun klien API yang tetap berdiri ketika jaringan sedang tidak bersahabat.