⏰ 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ż