⏰ Timeout Configuration

KNETTimeoutConfig pozwala na granularną kontrolę timeoutów dla różnych faz połączenia HTTP - connect, read, write i całkowity czas operacji.

💡 Kiedy używać?
  • Upload dużych plików - potrzebujesz długiego write timeout
  • Download dużych plików - potrzebujesz długiego read timeout
  • Streaming - brak timeout na read
  • Szybkie API - krótkie timeouty dla responsywności

📦 Import

import rip.nerd.kitsunenet.util.KNETTimeoutConfig
import rip.nerd.kitsunenet.util.withTimeout
import rip.nerd.kitsunenet.util.KNETTimeoutHelper

🚀 Szybki start

// Prosty timeout (wszystkie wartości takie same)
val timeout = KNETTimeoutConfig.simple(30_000) // 30 sekund

// Granularne timeouty
val timeout = KNETTimeoutConfig(
    connectMs = 10_000,    // 10s na nawiązanie połączenia
    readMs = 60_000,       // 60s na odczyt danych
    writeMs = 120_000,     // 120s na zapis (upload)
    callMs = 300_000       // 5min całkowity czas operacji
)

// Użyj z requestem
val request = KNETRequest.get("https://api.example.com/data")
    .withTimeout(timeout)

val response = client.request(request)

📋 Typy timeoutów

Timeout Opis Typowa wartość
connectMs Czas na nawiązanie połączenia TCP + TLS handshake 10-30 sekund
readMs Czas między pakietami danych przy odczycie 30-60 sekund (lub 0 dla streaming)
writeMs Czas między pakietami danych przy zapisie 30-120 sekund (zależnie od uploadu)
callMs Całkowity czas operacji od początku do końca Zależnie od operacji (lub null = brak limitu)
📝 Różnica między readMs a callMs

readMs to timeout między pakietami - jeśli dane nadchodzą ciągle, nie zostanie przekroczony nawet przy długim transferze. callMs to całkowity czas operacji - niezależnie od przepływu danych.

🎯 Presety

// Domyślny (30s wszystko)
val timeout = KNETTimeoutConfig.default()

// Szybkie API (krótkie timeouty)
val timeout = KNETTimeoutConfig.quick()

// Upload dużych plików
val timeout = KNETTimeoutConfig.forUpload()

// Download dużych plików
val timeout = KNETTimeoutConfig.forDownload()

// Streaming (nieograniczony read)
val timeout = KNETTimeoutConfig.forStreaming()

// WebSocket
val timeout = KNETTimeoutConfig.forWebSocket()

// Wolne API
val timeout = KNETTimeoutConfig.forSlowApi()

// Zoptymalizowane dla mobile
val timeout = KNETTimeoutConfig.forMobile()

// Brak timeoutów (ostrożnie!)
val timeout = KNETTimeoutConfig.none()
Preset Connect Read Write Call Użycie
default() 30s 30s 30s - Domyślne API
quick() 5s 10s 10s 15s Szybkie API
forUpload() 30s 30s 5min 10min Upload plików
forDownload() 30s 5min 30s 10min Download plików
forStreaming() 30s ∞ 30s ∞ SSE, streaming
forWebSocket() 15s ∞ 30s ∞ WebSocket
forSlowApi() 60s 2min 60s 5min Wolne serwery
forMobile() 15s 30s 30s 60s Oszczędność baterii

🔧 API Reference

KNETTimeoutConfig

data class KNETTimeoutConfig(
    val connectMs: Long = 30_000,
    val readMs: Long = 30_000,
    val writeMs: Long = 30_000,
    val callMs: Long? = null  // null = brak limitu
) {
    val totalMaxMs: Long      // Maksymalny czas (callMs lub suma)

    fun createOkHttpClient(): OkHttpClient
    fun applyTo(client: OkHttpClient): OkHttpClient
}

withTimeout() Extension

Extension function dla KNETRequest.

fun KNETRequest.withTimeout(config: KNETTimeoutConfig): KNETRequest
Przykład
val request = KNETRequest.get("https://api.example.com/data")
    .withTimeout(KNETTimeoutConfig.quick())

// Lub inline
val response = client.request(
    KNETRequest.get(url).withTimeout(KNETTimeoutConfig.forDownload())
)

KNETTimeoutHelper

Helpery do pracy z timeoutami.

calculateTimeout()

Oblicza timeout na podstawie rozmiaru danych.

fun calculateTimeout(
    dataSizeBytes: Long,
    bytesPerSecond: Long = 100_000,  // ~100KB/s
    minTimeoutMs: Long = 30_000,
    maxTimeoutMs: Long = 600_000
): Long
Przykład
val fileSize = 50 * 1024 * 1024L // 50 MB

val timeout = KNETTimeoutHelper.calculateTimeout(fileSize)
println("Obliczony timeout: ${timeout / 1000}s")
// Output: Obliczony timeout: 500s (dla 100KB/s)

forUploadSize() / forDownloadSize()

Tworzy config dla określonego rozmiaru.

fun forUploadSize(sizeBytes: Long): KNETTimeoutConfig
fun forDownloadSize(sizeBytes: Long): KNETTimeoutConfig
Przykład - dynamiczny timeout
// Upload pliku
val file = File("large_video.mp4")
val uploadTimeout = KNETTimeoutHelper.forUploadSize(file.length())

val request = uploadRequest.withTimeout(uploadTimeout)
val response = client.request(request)

// Download z Content-Length
val headResponse = client.head(downloadUrl)
val contentLength = headResponse.headers["Content-Length"]?.toLongOrNull() ?: 0

val downloadTimeout = KNETTimeoutHelper.forDownloadSize(contentLength)
val downloadRequest = KNETRequest.get(downloadUrl).withTimeout(downloadTimeout)

execute()

Wykonuje request z określonym timeout config.

suspend fun execute(
    request: KNETRequest,
    config: KNETTimeoutConfig
): KNETResponse

createClient()

Tworzy klienta z timeout config.

fun createClient(config: KNETTimeoutConfig): KNETClient

💡 Praktyczne przykłady

Upload z dynamicznym timeout

class FileUploader(private val client: KNETClient) {

    suspend fun uploadFile(file: File, url: String): Boolean {
        // Oblicz timeout na podstawie rozmiaru
        val timeout = KNETTimeoutHelper.forUploadSize(file.length())

        Log.d("Upload", "Plik: ${file.length() / 1024}KB, timeout: ${timeout.writeMs / 1000}s")

        // Utwórz klienta z tym timeout
        val uploadClient = KNETClient(okHttpClient = timeout.createOkHttpClient())

        return try {
            val response = uploadClient.post(url, mapOf(
                "file" to file.readBytes().toBase64()
            ))
            response.isSuccessful
        } catch (e: SocketTimeoutException) {
            Log.e("Upload", "Timeout - plik za duży lub połączenie za wolne")
            false
        }
    }
}

Streaming z nieograniczonym timeout

class StreamingClient {

    fun streamEvents(url: String, onEvent: (String) -> Unit) {
        val timeout = KNETTimeoutConfig.forStreaming()
        val client = KNETClient(okHttpClient = timeout.createOkHttpClient())

        // Streaming nigdy nie timeout'uje na read
        scope.launch {
            KNETBufferedStreamingClient.sse(url)
                .collect { event ->
                    onEvent(event.data)
                }
        }
    }
}

Różne timeouty per endpoint

class ApiClient {

    private val defaultClient = KNETClient()
    private val uploadClient = KNETClient(
        okHttpClient = KNETTimeoutConfig.forUpload().createOkHttpClient()
    )
    private val quickClient = KNETClient(
        okHttpClient = KNETTimeoutConfig.quick().createOkHttpClient()
    )

    // Szybkie operacje
    suspend fun getUser(id: String): User {
        return quickClient.get("$baseUrl/users/$id").json()
    }

    // Standardowe operacje
    suspend fun createUser(user: User): User {
        return defaultClient.post("$baseUrl/users", user.toMap()).json()
    }

    // Upload
    suspend fun uploadAvatar(userId: String, image: ByteArray): Boolean {
        return uploadClient.post(
            "$baseUrl/users/$userId/avatar",
            mapOf("image" to image.toBase64())
        ).isSuccessful
    }
}

Adaptive timeout na podstawie jakości połączenia

class AdaptiveClient(context: Context) {

    private val connectionQuality = KNETConnectionQuality.Builder(context).build()

    suspend fun request(request: KNETRequest): KNETResponse {
        val quality = connectionQuality.currentQuality

        // Dostosuj timeout do jakości połączenia
        val timeout = when (quality.level) {
            ConnectionLevel.EXCELLENT -> KNETTimeoutConfig.quick()
            ConnectionLevel.GOOD -> KNETTimeoutConfig.default()
            ConnectionLevel.FAIR -> KNETTimeoutConfig.forSlowApi()
            ConnectionLevel.POOR -> KNETTimeoutConfig(
                connectMs = 60_000,
                readMs = 180_000,
                writeMs = 180_000
            )
            ConnectionLevel.OFFLINE -> throw IOException("No connection")
        }

        val client = KNETClient(okHttpClient = timeout.createOkHttpClient())
        return client.request(request)
    }
}

⚠️ Obsługa błędów timeout

try {
    val response = client.request(request.withTimeout(KNETTimeoutConfig.quick()))
    // Sukces
} catch (e: SocketTimeoutException) {
    // Timeout na read/write
    when {
        e.message?.contains("connect") == true -> {
            showError("Nie można połączyć z serwerem")
        }
        e.message?.contains("read") == true -> {
            showError("Serwer nie odpowiada")
        }
        else -> {
            showError("Przekroczono czas oczekiwania")
        }
    }
} catch (e: IOException) {
    showError("Błąd sieci: ${e.message}")
}

📚 Zobacz też