Interceptory pozwalają modyfikować requesty i response w pipeline. KNET oferuje 12+ gotowych interceptorów.
// Interceptor to funkcja, która:
// 1. Otrzymuje request
// 2. Może go zmodyfikować
// 3. Przekazuje dalej w chain
// 4. Otrzymuje response
// 5. Może go zmodyfikować
// 6. Zwraca response
interface KNETInterceptor {
suspend fun intercept(
request: KNETRequest,
chain: suspend (KNETRequest) -> KNETResponse
): KNETResponse
}
Request → [Interceptor 1] → [Interceptor 2] → [Interceptor 3] → HTTP
↓
Response ← [Interceptor 1] ← [Interceptor 2] ← [Interceptor 3] ← HTTP
val client = KNETClient.builder()
.addInterceptor(KNETLoggingInterceptor())
.addInterceptor(KNETHeaderInterceptor.bearer(token))
.addInterceptor(KNETRetryInterceptor(maxRetries = 3))
.addInterceptor(KNETCacheInterceptor(cache))
.build()
Loguje requesty i response do Logcat.
val interceptor = KNETLoggingInterceptor(
level = LogLevel.BODY, // NONE, BASIC, HEADERS, BODY
tag = "KNET",
logger = { tag, msg -> Log.d(tag, msg) }
)
// Przykład output:
// --> POST https://api.example.com/users
// Content-Type: application/json
// {"name":"Jan"}
// --> END POST
// <-- 201 Created (150ms)
// {"id":1,"name":"Jan"}
// <-- END HTTP
Dodaje nagłówki do każdego requestu.
// Bearer token
val interceptor = KNETHeaderInterceptor.bearer(token)
// Basic auth
val interceptor = KNETHeaderInterceptor.basic(username, password)
// Custom headers
val interceptor = KNETHeaderInterceptor(mapOf(
"X-API-Key" to apiKey,
"X-Client-Version" to BuildConfig.VERSION_NAME,
"Accept-Language" to Locale.getDefault().language
))
// Dynamic headers
val interceptor = KNETHeaderInterceptor { request ->
mapOf(
"X-Request-ID" to UUID.randomUUID().toString(),
"X-Timestamp" to System.currentTimeMillis().toString()
)
}
Automatyczny retry przy błędach.
val interceptor = KNETRetryInterceptor(
maxRetries = 3,
retryDelayMs = 1000,
retryOnStatusCodes = setOf(500, 502, 503, 504),
retryOnExceptions = setOf(
SocketTimeoutException::class,
ConnectException::class
)
)
// Z exponential backoff
val interceptor = KNETExponentialBackoffInterceptor(
maxRetries = 5,
initialDelayMs = 1000,
maxDelayMs = 30000,
multiplier = 2.0,
jitter = true // Dodaje losowość
)
Cache odpowiedzi.
val cache = KNETMemoryCache(maxSize = 50)
val interceptor = KNETCacheInterceptor(
cache = cache,
defaultTtlMs = 300_000, // 5 min
cacheOnlyGet = true, // Cache tylko GET
respectCacheHeaders = true
)
Dynamiczne timeouty.
val interceptor = KNETTimeoutInterceptor(
defaultTimeoutMs = 30_000,
timeoutByHost = mapOf(
"slow-api.example.com" to 60_000,
"fast-api.example.com" to 10_000
),
timeoutByPath = mapOf(
"/upload" to 120_000,
"/download" to 300_000
)
)
Autoryzacja z auto-refresh tokena.
val interceptor = KNETAuthInterceptor(
tokenProvider = { authRepository.getAccessToken() },
refreshToken = {
authRepository.refreshToken()
authRepository.getAccessToken()
},
shouldRefresh = { response ->
response.statusCode == 401
}
)
Obsługa trybu offline.
val interceptor = KNETOfflineInterceptor(
isOnline = { networkChecker.isConnected() },
offlineCache = offlineCache,
queueOfflineRequests = true
)
Zbieranie metryk.
val metrics = KNETMetricsCollector()
val interceptor = KNETMetricsInterceptor(metrics)
// Później
val stats = metrics.getStats()
println("Total requests: ${stats.totalRequests}")
println("Success rate: ${stats.successRate}%")
println("Avg latency: ${stats.averageLatencyMs}ms")
Mockowanie odpowiedzi (do testów).
val interceptor = KNETMockInterceptor()
.mock("GET", "/users") {
KNETResponse(200, "OK", emptyMap(), """[{"id":1,"name":"Test"}]""")
}
.mock("POST", "/users") {
KNETResponse(201, "Created", emptyMap(), """{"id":2}""")
}
.mockError("GET", "/error") {
throw IOException("Simulated error")
}
Kompresja request body.
val interceptor = KNETCompressionInterceptor(
algorithm = CompressionAlgorithm.GZIP,
minSizeToCompress = 1024 // Kompresuj tylko > 1KB
)
Transformacja request/response.
val interceptor = KNETTransformInterceptor(
transformRequest = { request ->
// Dodaj timestamp do każdego body
request.copy(
data = (request.data as? Map<*, *>)?.plus(
"timestamp" to System.currentTimeMillis()
) ?: request.data
)
},
transformResponse = { response ->
// Parsuj i waliduj
response
}
)
Centralna obsługa błędów.
val interceptor = KNETErrorInterceptor { request, error ->
// Loguj błędy
analytics.logError("API Error", mapOf(
"url" to request.url,
"error" to error.message
))
// Możesz zwrócić fallback response
// lub rzucić dalej
throw error
}
class MyCustomInterceptor : KNETInterceptor {
override suspend fun intercept(
request: KNETRequest,
chain: suspend (KNETRequest) -> KNETResponse
): KNETResponse {
// 1. Przed requestem
val startTime = System.currentTimeMillis()
val modifiedRequest = request.copy(
headers = request.headers + ("X-Custom" to "value")
)
// 2. Wykonaj request
val response = chain(modifiedRequest)
// 3. Po response
val duration = System.currentTimeMillis() - startTime
Log.d("MyInterceptor", "${request.url} took ${duration}ms")
return response
}
}
// Użycie
val client = KNETClient.builder()
.addInterceptor(MyCustomInterceptor())
.build()
Kolejność interceptorów ma znaczenie! Pierwszy dodany jest wykonywany pierwszy dla requestu i ostatni dla response.
// Zalecana kolejność:
val client = KNETClient.builder()
.addInterceptor(KNETLoggingInterceptor()) // 1. Logowanie (widzi wszystko)
.addInterceptor(KNETMetricsInterceptor()) // 2. Metryki
.addInterceptor(KNETErrorInterceptor()) // 3. Obsługa błędów
.addInterceptor(KNETAuthInterceptor()) // 4. Auth (przed retry)
.addInterceptor(KNETRetryInterceptor()) // 5. Retry
.addInterceptor(KNETCacheInterceptor()) // 6. Cache (może pominąć sieć)
.addInterceptor(KNETCompressionInterceptor()) // 7. Kompresja (przed siecią)
.build()