← Semua materi
Hands-on LabAutomationMenengahBARU

Membuat retry aman di Bash dan curl: timeout, backoff, dan Retry-After

Bangun retry untuk request HTTP yang membedakan error sementara dari error permanen, menghormati Retry-After, membatasi waktu tunggu, dan menghindari duplikasi operasi.

Tujuan latihan

Membuat script Bash untuk request GET yang mencatat curl exit code serta HTTP status, hanya mengulang kondisi terpilih, menerapkan exponential backoff, dan berhenti dengan hasil yang dapat ditindaklanjuti.

Persiapan

Terminal Linux atau macOS dengan Bash, curl, awk, dan endpoint read-only yang boleh diuji. Contoh memakai example.com serta file lokal generik dan merupakan simulasi, bukan hasil pengujian produksi. Jangan menerapkan retry pada transaksi POST/PUT tanpa memahami idempotency dan dukungan API.

Satu request
Klasifikasi hasil
Retry-After atau backoff
Berhasil atau berhenti

Masalah

Retry dapat membuat automation lebih tahan terhadap timeout, rate limit, atau service unavailable. Namun retry yang terlalu cepat memperbesar beban, retry tanpa batas membuat job menggantung, dan retry pada operasi non-idempotent dapat menggandakan transaksi. Tujuannya bukan mengulang semua kegagalan, melainkan mengklasifikasikan hasil dan memberi jeda yang terukur hanya pada kondisi sementara.

Persiapan dan batas aman

Gunakan endpoint GET atau HEAD yang read-only. Tetapkan jumlah percobaan, timeout per request, total budget, serta status yang boleh diulang sebelum menjalankan script. Jangan menaruh token di source code atau menampilkan header Authorization. File header/body contoh dapat memuat data sensitif; gunakan direktori kerja yang aman dan kelola retensinya sesuai kebijakan.

1. Tentukan apakah request aman diulang

GET dan HEAD umumnya dipakai untuk membaca data, tetapi efek nyata tetap bergantung pada implementasi API. POST sering membuat resource atau transaksi baru sehingga retry dapat menggandakan operasi ketika respons pertama hilang setelah server memprosesnya. Untuk operasi write, gunakan idempotency key atau mekanisme vendor bila tersedia; jika tidak ada jaminan, hentikan dan minta status transaksi sebelum mengulang.

2. Ambil baseline tanpa retry

Tujuan langkah ini adalah melihat satu hasil asli sebelum menambahkan retry. --connect-timeout membatasi fase koneksi dan --max-time membatasi seluruh transfer. -D menyimpan response header, -o menyimpan body, dan --write-out mencetak status serta timing. Perhatikan curl exit code di shell, http_code, time_connect, dan time_total.

curl --silent --show-error \
  --connect-timeout 5 --max-time 15 \
  --dump-header retry-headers.txt \
  --output retry-body.txt \
  --write-out 'http=%{http_code} remote=%{remote_ip} connect=%{time_connect} total=%{time_total}\n' \
  https://example.com/
printf 'curl_exit=%s\n' "$?"

Contoh baseline (simulasi)

Contoh pertama menunjukkan HTTP berhasil dan curl exit code 0. Contoh kedua menunjukkan tidak ada respons HTTP sebelum timeout; http=000 bukan status HTTP dari server, melainkan penanda bahwa curl tidak menerima status.

http=200 remote=192.0.2.10 connect=0.041 total=0.183
curl_exit=0

http=000 remote=192.0.2.10 connect=0.000 total=15.002
curl_exit=28

3. Bedakan curl exit code dari HTTP status

curl exit code menjelaskan keberhasilan proses transfer, sedangkan HTTP status berasal dari server/proxy setelah request mencapai lapisan HTTP. Exit 0 masih dapat disertai HTTP 404 atau 500 kecuali opsi failure digunakan. Sebaliknya, exit 6, 7, 28, atau error TLS dapat terjadi tanpa HTTP status. Automation harus mencatat keduanya agar 404 tidak disamakan dengan timeout.

4. Coba retry bawaan curl untuk GET sederhana

curl --retry menyediakan retry untuk error sementara yang dikenali curl. --retry-max-time membatasi waktu retry dan --max-time tetap berlaku per transfer. Gunakan command ini ketika perilaku bawaan cukup dan request aman diulang. Jangan langsung menambahkan --retry-all-errors karena itu dapat mengulang kegagalan yang tidak semestinya, terutama bila request bukan read-only.

curl --silent --show-error --fail-with-body \
  --connect-timeout 5 --max-time 15 \
  --retry 3 --retry-max-time 45 \
  https://example.com/

5. Pahami delay bawaan dan kebutuhan kontrol khusus

curl dapat menunda retry secara progresif untuk kondisi yang didukung dan dapat membaca Retry-After pada respons yang relevan. Namun automation sering membutuhkan audit yang lebih jelas: status apa yang diulang, delay berapa, nomor attempt, dan kapan job berhenti. Jika kebutuhan itu penting, gunakan loop Bash eksplisit pada langkah berikutnya daripada menumpuk opsi tanpa memahami interaksinya.

6. Tentukan daftar kegagalan yang retryable

Contoh script akan mengulang HTTP 408, 429, 500, 502, 503, dan 504 serta beberapa transport error yang lazim dianggap sementara dalam konteks lab: DNS resolution, connect failure, timeout, empty reply, dan receive error. HTTP 400, 401, 403, dan 404 tidak diulang karena biasanya memerlukan koreksi request, credential, policy, atau URL. Daftar ini adalah kebijakan contoh; sesuaikan dengan kontrak API dan jangan menganggap semua 5xx aman untuk write operation.

7. Buat kerangka loop dengan batas percobaan

Tujuan loop adalah memastikan automation selalu berhenti. Script menggunakan empat attempt, mencatat timestamp UTC, dan menyimpan header/body terbaru. set -u membantu mendeteksi variable yang belum didefinisikan, sedangkan pipefail menjaga kegagalan pipeline tidak tersembunyi. set -e sengaja tidak digunakan karena curl failure perlu ditangani sebagai data.

#!/usr/bin/env bash
set -u -o pipefail

URL='https://example.com/'
MAX_ATTEMPTS=4
BASE_DELAY=2
MAX_DELAY=16
HEADERS='retry-headers.txt'
BODY='retry-body.txt'

for ((attempt=1; attempt<=MAX_ATTEMPTS; attempt++)); do
  printf 'time=%s attempt=%d/%d url=%s\n' \
    "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$attempt" "$MAX_ATTEMPTS" "$URL"
  # Request dan keputusan ditambahkan pada langkah berikutnya.
done

8. Jalankan request dan tangkap dua jenis status

Command substitution menyimpan HTTP status dari --write-out. Setelah curl selesai, $? harus segera disimpan sebagai curl_rc agar tidak tertimpa command lain. Hasil normal adalah curl_rc=0 dan status 2xx. Jika curl_rc non-zero, body atau header mungkin tidak lengkap; interpretasikan berdasarkan kode curl dan stderr.

http_code=$(curl --silent --show-error \
  --connect-timeout 5 --max-time 15 \
  --dump-header "$HEADERS" \
  --output "$BODY" \
  --write-out '%{http_code}' \
  "$URL")
curl_rc=$?

printf 'curl_exit=%d http=%s\n' "$curl_rc" "$http_code"

9. Hentikan segera saat request berhasil

Contoh menganggap HTTP 200–299 sebagai sukses. Jika API Anda mengharapkan 304 atau status khusus lain, definisikan secara eksplisit. Jangan menganggap semua 3xx sukses karena redirect dapat menuju login atau lokasi yang tidak diharapkan. Ketika kondisi sukses terpenuhi, keluar dengan status 0 agar caller mengetahui job berhasil.

if (( curl_rc == 0 )) && [[ "$http_code" =~ ^2[0-9][0-9]$ ]]; then
  printf 'result=success attempt=%d http=%s\n' "$attempt" "$http_code"
  exit 0
fi

10. Klasifikasikan hasil yang boleh diulang

Gunakan dua flag terpisah untuk HTTP dan transport. Jika hasil tidak termasuk daftar retryable, hentikan lebih awal dengan exit non-zero dan pesan yang jelas. Ini mencegah 401 akibat token salah diulang empat kali atau 404 membebani endpoint yang tidak ada.

retryable=no
case "$http_code" in
  408|429|500|502|503|504) retryable=yes ;;
esac
case "$curl_rc" in
  6|7|28|52|56) retryable=yes ;;
esac

if [[ "$retryable" != yes ]]; then
  printf 'result=permanent_failure curl_exit=%d http=%s\n' "$curl_rc" "$http_code" >&2
  exit 1
fi

11. Baca Retry-After secara defensif

Retry-After dapat berisi jumlah detik atau tanggal HTTP. Contoh sederhana hanya menerima angka agar parsing tidak bergantung locale/date implementation. Jika nilainya angka dan masih dalam budget, gunakan nilainya. Jika server meminta waktu lebih panjang dari MAX_DELAY, script berhenti dan menyerahkan penjadwalan ke sistem lain; jangan diam-diam menunggu lebih singkat dari permintaan server.

retry_after=$(awk 'BEGIN{IGNORECASE=1} /^Retry-After:/ {gsub("\\r",""); print $2; exit}' "$HEADERS")

delay=''
if [[ "$retry_after" =~ ^[0-9]+$ ]]; then
  if (( retry_after > MAX_DELAY )); then
    printf 'result=deferred retry_after=%s exceeds_budget=%s\n' "$retry_after" "$MAX_DELAY" >&2
    exit 1
  fi
  delay=$retry_after
fi

Contoh respons 429 (simulasi)

Header berikut hanya simulasi. HTTP 429 menunjukkan terlalu banyak request dalam periode tertentu; Retry-After: 30 meminta client menunggu 30 detik. Jika budget script hanya 16 detik, keputusan aman adalah berhenti/defer, bukan mengabaikan header atau mengulang setelah 16 detik.

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 30

12. Gunakan exponential backoff dengan jitter bila header tidak tersedia

Jika tidak ada Retry-After numerik, hitung 2, 4, 8, lalu maksimal 16 detik dan tambahkan jitter kecil. Backoff mengurangi tekanan saat service bermasalah; jitter mencegah banyak worker bangun bersamaan. Jika attempt terakhir sudah tercapai, jangan sleep lagi—keluar gagal dengan ringkasan.

if (( attempt == MAX_ATTEMPTS )); then
  printf 'result=retry_exhausted attempts=%d curl_exit=%d http=%s\n' \
    "$attempt" "$curl_rc" "$http_code" >&2
  exit 1
fi

if [[ -z "$delay" ]]; then
  delay=$(( BASE_DELAY * (2 ** (attempt - 1)) ))
  (( delay > MAX_DELAY )) && delay=$MAX_DELAY
  jitter=$(( RANDOM % 3 ))
  delay=$(( delay + jitter ))
fi

printf 'result=retry_wait seconds=%d\n' "$delay"
sleep "$delay"

13. Satukan script dan periksa syntax

Masukkan blok langkah 8–12 di dalam loop langkah 7 sesuai urutan. Setelah menyimpan sebagai retry-safe.sh, validasi syntax tanpa menjalankan request menggunakan bash -n. Jika command mengembalikan exit 0 dan tidak mencetak error, syntax dasar valid. Ini belum membuktikan logika atau endpoint benar.

bash -n retry-safe.sh
printf 'syntax_exit=%s\n' "$?"
chmod 750 retry-safe.sh

14. Uji success, permanent failure, dan transient failure

Uji minimal tiga jalur pada endpoint lab yang memang disediakan untuk testing: sukses 2xx, error permanen seperti 404, dan error sementara seperti 429/503 dengan Retry-After. Jangan memalsukan kegagalan pada production atau mengirim burst. Perhatikan attempt, curl_exit, http, delay, dan final exit status. Success harus berhenti segera; permanent failure tidak boleh retry; transient failure harus mengikuti budget lalu sukses atau retry_exhausted.

15. Pisahkan retry dari scheduler

Retry menangani gangguan singkat di dalam satu job. Jika Retry-After melebihi budget atau semua attempt habis, serahkan percobaan berikutnya ke scheduler/queue dengan status gagal yang dapat diamati. Menahan satu process selama berjam-jam menyulitkan operasi, memakai worker, dan membuat timeout platform bertabrakan dengan timeout script.

16. Lindungi secret dan evidence

Jangan menjalankan script dengan set -x ketika header berisi token. Hindari token di argument command line jika lingkungan memungkinkan secret file atau environment yang dikelola. Log cukup berisi timestamp, attempt, endpoint tanpa query sensitif, curl exit, HTTP status, delay, dan result. Review retry-body.txt serta retry-headers.txt sebelum dibagikan karena keduanya dapat memuat identifier, cookie, atau detail internal.

Decision path

Jika curl_exit non-zero dan termasuk daftar sementara, retry dengan backoff; jika kode menunjukkan certificate verification atau konfigurasi lokal, hentikan dan perbaiki penyebab. Jika HTTP 429/503 memiliki Retry-After numerik dalam budget, ikuti nilainya; jika melebihi budget, defer ke scheduler. Jika HTTP 401/403/404, jangan retry—periksa credential, policy, atau URL. Jika request write tidak memiliki idempotency protection, jangan retry otomatis meskipun status terlihat sementara.

Kesalahan umum

Jangan memakai --retry-all-errors tanpa memahami method dan failure mode. Jangan mengulang POST transaksi secara buta. Jangan memakai timeout tak terbatas, sleep tetap tanpa backoff, atau loop while true. Jangan menganggap http=000 sebagai status server. Jangan mengabaikan Retry-After, dan jangan menunggu lebih pendek dari nilai server hanya karena ada cap internal. Jangan mencetak Authorization header, body sensitif, atau query token ke log.

Checklist verifikasi

Pastikan request read-only atau memiliki idempotency protection; connect dan total timeout ditetapkan; attempt dan total budget dibatasi; curl exit serta HTTP status dicatat terpisah; status permanen berhenti segera; status sementara saja yang retry; Retry-After dihormati; fallback memakai exponential backoff dan jitter; attempt terakhir tidak sleep; secret tidak masuk log; serta caller menerima exit 0 hanya untuk hasil yang benar-benar sukses.

Pelajaran

Retry yang aman adalah keputusan berbasis bukti, bukan pengulangan buta. Automation harus memahami jenis operasi, membedakan transport error dari HTTP response, menghormati instruksi server, mengurangi tekanan melalui backoff, dan selalu memiliki batas waktu serta hasil akhir yang jelas.

Referensi resmi

curl — manual resmi ↗RFC 9110 — HTTP Semantics ↗RFC 6585 — 429 Too Many Requests ↗GNU Bash — Reference Manual ↗
Lihat semua materi →