4.1
Option pada httpclient
httpclient tidak memakai struct konfigurasi terpisah. Seluruh perilaku klien diatur lewat satu tipe fungsi, Option, yang dideklarasikan sebagai type Option func(*Client) httpclient/options.go:11-121. Setiap fungsi With... mengembalikan closure yang menerima *Client dan menulis satu field secara langsung. NewClient membuat Client dengan nilai default, menjalankan setiap option berurutan, lalu memanggil client.updateHTTPTimeout() sekali di akhir httpclient/client.go:35-492.
Arah setiap option
graph LR
subgraph opts["Option: func(*Client)"]
direction TB
o1["WithHTTPTimeout"]
o2["WithHTTPClient"]
o3["WithRetryCount"]
o4["WithRetrier"]
o5["WithRetryableStatusCodes"]
o6["WithRetryErrorBudgetToken"]
o7["WithRetryErrorBudgetPercent"]
end
o1 --> f1["c.timeout *time.Duration"]
o2 --> f2["c.client heimdall.Doer"]
o3 --> f3["c.retryCount int"]
o4 --> f4["c.retrier heimdall.Retriable"]
o5 --> f5["c.retryableCodes []int"]
o6 --> f6["c.retryErrorBudget internal.ErrorBudget"]
o7 --> f6
f1 --> u["updateHTTPTimeout"]
f2 --> u
u --> t["http.Client.Timeout"]
f3 --> d["Client.Do retry loop"]
f4 --> d
f5 --> d
f6 --> s["skipRetry"]
s --> d
style o1 fill:#f59e0b,color:#000
style o2 fill:#f59e0b,color:#000
style o3 fill:#f59e0b,color:#000
style o4 fill:#f59e0b,color:#000
style o5 fill:#f59e0b,color:#000
style o6 fill:#f59e0b,color:#000
style o7 fill:#f59e0b,color:#000
style f1 fill:#336791,color:#fff
style f2 fill:#336791,color:#fff
style f3 fill:#336791,color:#fff
style f4 fill:#336791,color:#fff
style f5 fill:#336791,color:#fff
style f6 fill:#8b5cf6,color:#fff
style u fill:#8b5cf6,color:#fff
style t fill:#2d7d46,color:#fff
style d fill:#2d7d46,color:#fff
style s fill:#dc2626,color:#fff
%% fig: Sambungan setiap Option ke field Client
%% desc: Tujuh fungsi With menulis enam field Client; timeout dan client bermuara ke updateHTTPTimeout, sisanya dipakai di loop Do dan skipRetry.Tujuh fungsi With... hanya menulis field yang sudah ada pada struct Client: client, timeout, retrier, retryCount, retryableCodes, dan retryErrorBudget httpclient/client.go:16-253. Dua option berhenti di tahap konfigurasi, yaitu WithHTTPTimeout dan WithHTTPClient, karena keduanya memicu updateHTTPTimeout yang menyalin c.timeout ke http.Client.Timeout httpclient/client.go:222-2294. Empat option lain baru terpakai saat request berjalan: retryCount menentukan panjang loop Do, retrier memasok jeda, sedangkan retryableCodes dan retryErrorBudget menentukan apakah percobaan diulang atau dihentikan.
Daftar option
| Option | Field tujuan | Perilaku |
|---|---|---|
WithHTTPTimeout | c.timeout *time.Duration | Menyimpan durasi lalu memanggil c.updateHTTPTimeout() httpclient/options.go:15-215 |
WithRetryCount | c.retryCount int | Menetapkan jumlah percobaan ulang httpclient/options.go:24-286 |
WithRetrier | c.retrier heimdall.Retriable | Menetapkan strategi jeda antar percobaan httpclient/options.go:31-357 |
WithHTTPClient | c.client heimdall.Doer | Mengganti implementasi HTTP client, lalu memanggil c.updateHTTPTimeout() httpclient/options.go:38-448 |
WithRetryableStatusCodes | c.retryableCodes []int | Menambahkan status code ke daftar lalu mengurutkannya dengan slices.Sort httpclient/options.go:46-559 |
WithRetryErrorBudgetToken | c.retryErrorBudget *internal.ErrorBudget | Membuat budget lewat internal.NewTokenErrorBudget(maxToken, tokenRatio) httpclient/options.go:61-6510 |
WithRetryErrorBudgetPercent | c.retryErrorBudget *internal.ErrorBudget | Membuat budget lewat internal.NewPercentErrorBudget(minFailureVolume, failurePercent) httpclient/options.go:82-8611 |
WithRetryCount juga berpengaruh pada penyiapan request: Do hanya memanggil internal.SetRequestGetBody ketika c.retryCount > 0, sehingga request tanpa retry tidak menanggung overhead tambahan httpclient/client.go:133-13912. Rincian loop dan jedanya ada di Siklus Request dan Retry serta Retry dan Backoff.
Status code yang diulang
WithRetryableStatusCodes menerima variadic statusCodes ...int, menggabungkannya dengan daftar yang sudah ada, lalu mengurutkan hasilnya httpclient/options.go:46-559. Pengurutan itu bukan hiasan: Do memeriksa daftar tersebut dengan slices.BinarySearch httpclient/client.go:179-18113. Selain daftar itu, seluruh status 5xx selalu layak diulang, seperti dicatat pada komentar option-nya: "All 5xx status codes are always eligible for retry, thus not required for WithRetryableStatusCodes option." httpclient/options.go:46-4714
Test TestOptionsAreSet memasang WithRetryableStatusCodes(400, 201, 424) dan memastikan field akhirnya berisi []int{201, 400, 424}, bukti bahwa urutan masukan tidak dipertahankan httpclient/options_test.go:13-3815.
Timeout: WithHTTPTimeout berhadapan dengan WithHTTPClient
updateHTTPTimeout langsung kembali bila c.timeout == nil, dan hanya menulis bila c.client benar-benar bertipe *http.Client httpclient/client.go:222-2294. Dari situ muncul dua perilaku yang perlu diingat.
| Skenario option | c.timeout | Timeout pada HTTP client |
|---|---|---|
Hanya WithHTTPClient(client) | nil | Timeout pemanggil dipertahankan, 25 * time.Millisecond httpclient/options_test.go:40-5216 |
WithHTTPClient lalu WithHTTPTimeout(10 * time.Second) | 10 * time.Second | 10 * time.Second httpclient/options_test.go:54-6917 |
WithHTTPTimeout lalu WithHTTPClient | 10 * time.Second | 10 * time.Second httpclient/options_test.go:71-8618 |
Urutan dua option itu tidak mengubah hasil untuk *http.Client, karena NewClient menutup rangkaian option dengan satu panggilan updateHTTPTimeout httpclient/client.go:35-492. Yang tidak bisa ditembus adalah implementasi Doer buatan sendiri: bila c.client bukan *http.Client, timeout tidak pernah ditulis ke dalamnya, sesuai komentar pengujian "can't override custom implementation" httpclient/options_test.go:13-3815.
⚠️
WithHTTPTimeouttidak mengubah timeout di dalamDoerkustom. Nilai timeout milik implementasi buatan sendiri tetap berlaku, sehinggac.timeoutbisa berbeda dari timeout yang benar-benar dipakai. Pasang timeout di dalam tipe kustom itu sendiri bila memakaiWithHTTPClient.
Pola yang sama dipakai klien circuit breaker pada Option dan Fallback hystrix, karena hystrix.WithHTTPTimeout bersandar pada mekanisme ini, seperti ditulis pada komentar di dalam WithHTTPTimeout httpclient/options.go:15-215.
Nilai default tanpa option
NewClient mengisi client dengan &http.Client{Timeout: defaultHTTPTimeout}, retryCount dengan defaultRetryCount, dan retrier dengan heimdall.NewNoRetrier(); dua konstanta default itu bernilai 0 dan 30 * time.Second httpclient/client.go:27-3019. TestOptionsHaveDefaults memastikan c.timeout tetap nil dan c.retrier adalah NewNoRetrier() ketika NewClient() dipanggil tanpa argumen httpclient/options_test.go:88-10520.
Karena timeout dibiarkan nil, updateHTTPTimeout tidak menyentuh http.Client bawaan, dan timeout 30 detik dari literal di NewClient tetap utuh httpclient/client.go:35-492.
Batas budget token
WithRetryErrorBudgetToken(maxToken int32, tokenRatio float32) meneruskan nilainya ke internal.NewTokenErrorBudget httpclient/options.go:61-6510, dan konstruktor itu mem-panic bila maxToken melampaui maxAllowedToken, yaitu ((1 << 30) - 1) / normaliser dengan normaliser = 10000 internal/error_budget.go:8-921. Ambang itu bernilai 107374, jadi WithRetryErrorBudgetToken(200000, 1) akan gagal saat option dijalankan, bukan saat request pertama.
⚠️ Pelanggaran batas token memicu panic dengan pesan "max token exceeds allowed limit (%d)" dari konstruktor
internal.NewTokenErrorBudgetinternal/error_budget.go:25-2822. ValidasimaxTokensebelum membangun klien, jangan mengandalkan error yang dikembalikanDo.
WithRetryErrorBudgetPercent tidak meminta maxToken; ia menghitungnya dari minFailureVolume * 2 di dalam internal.NewPercentErrorBudget httpclient/options.go:82-8611. Kedua option menulis field yang sama, c.retryErrorBudget, sehingga option yang dijalankan terakhir menang. Pemakaian field itu di skipRetry dijelaskan di Error Budget untuk Retry.
Contoh pemakaian
Pengujian TestOptionsAreSet merangkai lima option sekaligus dan memeriksa setiap field hasilnya httpclient/options_test.go:13-3815. Contoh yang lebih lengkap, dengan WithHTTPClient yang membungkus http.Client milik pemanggil, ada di berkas contoh repository:
httpClient := httpclient.NewClient(
httpclient.WithHTTPTimeout(0*time.Millisecond),
httpclient.WithHTTPClient(&myHTTPClient{
client: http.Client{Timeout: 25 * time.Millisecond},
}),
httpclient.WithRetryCount(2),
httpclient.WithRetrier(heimdall.NewRetrier(heimdall.NewConstantBackoff(10*time.Millisecond, 50*time.Millisecond))),
)
Perhatikan bahwa WithHTTPTimeout dipanggil lebih dulu daripada WithHTTPClient di contoh tersebut; hasil akhirnya tetap konsisten karena penutup rangkaian di NewClient examples/client.go:82-9123.