KNET oferuje różne strategie automatycznego ponawiania requestów przy błędach.
import rip.nerd.kitsunenet.interceptor.KNETRetryInterceptor
val client = KNETClient.builder()
.addInterceptor(KNETRetryInterceptor(
maxRetries = 3,
retryDelayMs = 1000
))
.build()
// Request będzie ponawiany do 3 razy
// z 1s delay między próbami
val client = KNETClient.builder()
.addInterceptor(KNETExponentialBackoffInterceptor(
maxRetries = 5,
initialDelayMs = 1000, // 1s
maxDelayMs = 30_000, // Max 30s
multiplier = 2.0 // x2 każdy retry
))
.build()
// Delays: 1s → 2s → 4s → 8s → 16s
val client = KNETClient.builder()
.addInterceptor(KNETExponentialBackoffInterceptor(
maxRetries = 5,
initialDelayMs = 1000,
maxDelayMs = 30_000,
multiplier = 2.0,
jitter = true, // Dodaj losowość
jitterFactor = 0.5 // ±50%
))
.build()
// Delays z jitter: ~1s → ~2s → ~4s → ...
// Zapobiega "thundering herd" problem
val interceptor = KNETRetryInterceptor(
maxRetries = 3,
retryDelayMs = 1000,
// Statusy HTTP do retry
retryOnStatusCodes = setOf(
408, // Request Timeout
429, // Too Many Requests
500, // Internal Server Error
502, // Bad Gateway
503, // Service Unavailable
504 // Gateway Timeout
),
// Wyjątki do retry
retryOnExceptions = setOf(
SocketTimeoutException::class,
ConnectException::class,
UnknownHostException::class,
SSLException::class
),
// Metody HTTP do retry (idempotentne)
retryMethods = setOf("GET", "HEAD", "OPTIONS", "PUT", "DELETE"),
// Nie ponawiaj POST (nie idempotentne)
// chyba że request ma X-Idempotency-Key
retryPostWithIdempotencyKey = true
)
val interceptor = KNETRetryInterceptor(
maxRetries = 3,
shouldRetry = { request, response, exception, attempt ->
when {
// Nie retry przy 4xx (client error)
response?.statusCode in 400..499 -> false
// Retry przy server errors
response?.statusCode in 500..599 -> true
// Retry przy network errors
exception is IOException -> true
// Nie retry przy 3+ próbie dla slow endpoints
request.url.contains("/slow") && attempt >= 2 -> false
else -> true
}
}
)
val interceptor = KNETRetryInterceptor(
maxRetries = 3,
onRetry = { request, attempt, delay, error ->
Log.w("Retry", "Attempt $attempt for ${request.url}")
Log.w("Retry", "Error: ${error.message}")
Log.w("Retry", "Waiting ${delay}ms...")
// Analytics
analytics.log("api_retry", mapOf(
"url" to request.url,
"attempt" to attempt,
"error" to error.javaClass.simpleName
))
},
onMaxRetriesExceeded = { request, error ->
Log.e("Retry", "Max retries exceeded for ${request.url}")
analytics.log("api_max_retries_exceeded")
}
)
val interceptor = KNETRetryInterceptor(
maxRetries = 5,
delayStrategy = { attempt, lastError, response ->
when {
// Rate limit - użyj Retry-After
response?.statusCode == 429 -> {
val retryAfter = response.headers["Retry-After"]
?.toLongOrNull()?.times(1000)
retryAfter ?: (attempt * 5000L)
}
// Timeout - szybki retry
lastError is SocketTimeoutException -> {
500L // 0.5s
}
// Connection error - wolniejszy retry
lastError is ConnectException -> {
attempt * 2000L // 2s, 4s, 6s...
}
// Default exponential
else -> {
minOf(1000L * (1 shl (attempt - 1)), 30_000L)
}
}
}
)
// Nadpisz config dla konkretnego requesta
val request = KNETRequest.get(url)
.withRetry(maxRetries = 5)
.withRetryDelay(2000)
// Lub wyłącz retry
val request = KNETRequest.post(url, data)
.noRetry()
class ApiService(private val client: KNETClient) {
// Konfiguracja per-endpoint
suspend fun getUsers(): List<User> {
// Szybki retry dla prostych GET
return withRetry(maxRetries = 3, delay = 500) {
client.get("$baseUrl/users").jsonList()
}
}
suspend fun createOrder(order: Order): Order {
// Dłuższy retry dla ważnych operacji
return withRetry(
maxRetries = 5,
delay = 2000,
exponential = true
) {
client.post("$baseUrl/orders", order.toJson()).json()
}
}
private suspend fun <T> withRetry(
maxRetries: Int,
delay: Long,
exponential: Boolean = false,
block: suspend () -> T
): T {
var lastError: Exception? = null
var currentDelay = delay
repeat(maxRetries) { attempt ->
try {
return block()
} catch (e: Exception) {
lastError = e
Log.w("API", "Retry ${attempt + 1}/$maxRetries: ${e.message}")
if (attempt < maxRetries - 1) {
delay(currentDelay)
if (exponential) {
currentDelay = minOf(currentDelay * 2, 30_000)
}
}
}
}
throw lastError ?: Exception("Max retries exceeded")
}
}
| Strategia | Delays | Użycie |
|---|---|---|
| Simple | 1s, 1s, 1s | Szybkie błędy serwera |
| Exponential | 1s, 2s, 4s, 8s | Przeciążony serwer |
| Exp + Jitter | ~1s, ~2s, ~4s | Wiele klientów naraz |
| Linear | 1s, 2s, 3s, 4s | Stopniowe odciążenie |