CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-ktor--ktor-client-core-iosx64

Ktor HTTP client core library - asynchronous framework for creating HTTP clients in Kotlin multiplatform

Pending
Overview
Eval results
Files

http-caching.mddocs/

HTTP Caching

Response caching with configurable storage backends, cache control support, and HTTP-compliant caching behavior for improved performance.

Capabilities

HttpCache Plugin

Core plugin for HTTP response caching with public and private storage options.

/**
 * HTTP response caching plugin
 */
class HttpCache internal constructor(
    private val publicStorage: CacheStorage,
    private val privateStorage: CacheStorage,
    private val isSharedClient: Boolean
) {
    /**
     * Cache configuration
     */
    class Config {
        /** Whether client is shared between multiple users */
        var isShared: Boolean = false
        
        /**
         * Configure public cache storage
         */
        fun publicStorage(storage: CacheStorage)
        
        /**
         * Configure private cache storage  
         */
        fun privateStorage(storage: CacheStorage)
    }
    
    companion object : HttpClientPlugin<Config, HttpCache> {
        override val key: AttributeKey<HttpCache> = AttributeKey("HttpCache")
        
        /** Event fired when response is served from cache */
        val HttpResponseFromCache: EventDefinition<HttpResponse>
    }
}

Usage Examples:

import io.ktor.client.plugins.cache.*

val client = HttpClient {
    install(HttpCache) {
        // Configure as shared client
        isShared = false
        
        // Use unlimited cache storage
        publicStorage(CacheStorage.Unlimited())
        privateStorage(CacheStorage.Unlimited())
    }
    
    // Monitor cache hits
    monitor.subscribe(HttpCache.HttpResponseFromCache) { response ->
        println("Cache hit for: ${response.request.url}")
    }
}

// First request - fetches from server and caches
val response1 = client.get("https://api.example.com/data")
println("First request: ${response1.status}")

// Second request - served from cache if cacheable
val response2 = client.get("https://api.example.com/data")
println("Second request: ${response2.status}")

CacheStorage Interface

Modern cache storage interface for storing and retrieving cached responses.

/**
 * Cache storage interface for HTTP responses
 */
interface CacheStorage {
    /**
     * Store cached response data
     */
    suspend fun store(url: Url, data: CachedResponseData)
    
    /**
     * Find cached response with vary key matching
     */
    suspend fun find(url: Url, varyKeys: Map<String, String>): CachedResponseData?
    
    /**
     * Find all cached responses for URL
     */
    suspend fun findAll(url: Url): Set<CachedResponseData>
    
    companion object {
        /** Create unlimited in-memory cache storage */
        fun Unlimited(): CacheStorage
        
        /** Disabled cache storage (no-op) */
        val Disabled: CacheStorage
    }
}

Built-in Cache Storage Implementations

Pre-built storage implementations for different caching strategies.

/**
 * Unlimited in-memory cache storage
 */
class UnlimitedStorage : CacheStorage {
    override suspend fun store(url: Url, data: CachedResponseData)
    override suspend fun find(url: Url, varyKeys: Map<String, String>): CachedResponseData?
    override suspend fun findAll(url: Url): Set<CachedResponseData>
}

/**
 * Disabled cache storage (no-op implementation)
 */
object DisabledStorage : CacheStorage {
    override suspend fun store(url: Url, data: CachedResponseData) {}
    override suspend fun find(url: Url, varyKeys: Map<String, String>): CachedResponseData? = null
    override suspend fun findAll(url: Url): Set<CachedResponseData> = emptySet()
}

Usage Examples:

// Unlimited caching
val unlimitedClient = HttpClient {
    install(HttpCache) {
        publicStorage(CacheStorage.Unlimited())
        privateStorage(CacheStorage.Unlimited())
    }
}

// Disabled caching
val noCacheClient = HttpClient {
    install(HttpCache) {
        publicStorage(CacheStorage.Disabled)
        privateStorage(CacheStorage.Disabled)
    }
}

// Custom cache storage with size limit
class LimitedCacheStorage(private val maxEntries: Int) : CacheStorage {
    private val cache = LinkedHashMap<String, CachedResponseData>()
    
    override suspend fun store(url: Url, data: CachedResponseData) {
        val key = url.toString()
        if (cache.size >= maxEntries && key !in cache) {
            // Remove oldest entry
            cache.remove(cache.keys.first())
        }
        cache[key] = data
    }
    
    override suspend fun find(url: Url, varyKeys: Map<String, String>): CachedResponseData? {
        return cache[url.toString()]
    }
    
    override suspend fun findAll(url: Url): Set<CachedResponseData> {
        return cache[url.toString()]?.let { setOf(it) } ?: emptySet()
    }
}

val limitedClient = HttpClient {
    install(HttpCache) {
        publicStorage(LimitedCacheStorage(maxEntries = 100))
        privateStorage(LimitedCacheStorage(maxEntries = 50))
    }
}

CachedResponseData Class

Data class representing cached HTTP response with metadata.

/**
 * Cached response data with HTTP metadata
 */
data class CachedResponseData(
    /** Original request URL */
    val url: Url,
    
    /** Response status code */
    val statusCode: HttpStatusCode,
    
    /** Request timestamp */
    val requestTime: GMTDate,
    
    /** Response timestamp */
    val responseTime: GMTDate,
    
    /** HTTP protocol version */
    val version: HttpProtocolVersion,
    
    /** Cache expiration time */
    val expires: GMTDate,
    
    /** Response headers */
    val headers: Headers,
    
    /** Vary header matching keys */
    val varyKeys: Map<String, String>,
    
    /** Response body content */
    val body: ByteArray
)

Usage Examples:

// Create cached response data manually
val cachedData = CachedResponseData(
    url = Url("https://api.example.com/data"),
    statusCode = HttpStatusCode.OK,
    requestTime = GMTDate(),
    responseTime = GMTDate(),
    version = HttpProtocolVersion.HTTP_1_1,
    expires = GMTDate() + 3600_000, // 1 hour from now
    headers = headersOf(
        HttpHeaders.ContentType, "application/json",
        HttpHeaders.CacheControl, "public, max-age=3600"
    ),
    varyKeys = mapOf(
        "Accept-Language" to "en-US",
        "Accept-Encoding" to "gzip"
    ),
    body = """{"message": "cached data"}""".toByteArray()
)

// Store in cache manually
val storage = CacheStorage.Unlimited()
storage.store(cachedData.url, cachedData)

// Retrieve from cache
val retrieved = storage.find(
    url = Url("https://api.example.com/data"),
    varyKeys = mapOf(
        "Accept-Language" to "en-US",
        "Accept-Encoding" to "gzip"
    )
)

if (retrieved != null) {
    println("Found cached data: ${String(retrieved.body)}")
    println("Expires: ${retrieved.expires}")
}

Cache Control Headers

Support for HTTP cache control directives and validation.

/**
 * Cache storage extensions for response handling
 */
suspend fun CacheStorage.store(response: HttpResponse): CachedResponseData
suspend fun CacheStorage.store(
    response: HttpResponse,
    varyKeys: Map<String, String>
): CachedResponseData
suspend fun CacheStorage.store(
    response: HttpResponse,
    varyKeys: Map<String, String>,
    isShared: Boolean
): CachedResponseData

Usage Examples:

val client = HttpClient {
    install(HttpCache) {
        publicStorage(CacheStorage.Unlimited())
    }
}

// Responses with cache headers are automatically cached
val response1 = client.get("https://api.example.com/public-data") {
    headers {
        // Request headers that might affect caching
        append(HttpHeaders.AcceptLanguage, "en-US")
        append(HttpHeaders.AcceptEncoding, "gzip")
    }
}

// Check if response was cached
val cacheControl = response1.headers[HttpHeaders.CacheControl]
println("Cache-Control: $cacheControl")

// Conditional requests with cache validation
val response2 = client.get("https://api.example.com/data") {
    headers {
        // Add conditional headers for cache validation
        append(HttpHeaders.IfModifiedSince, lastModified)
        append(HttpHeaders.IfNoneMatch, etag)
    }
}

// Handle 304 Not Modified responses
if (response2.status == HttpStatusCode.NotModified) {
    println("Content not modified, using cached version")
}

Cache Events and Monitoring

Events for monitoring cache behavior and performance.

/**
 * Event fired when response is served from cache
 */
val HttpCache.HttpResponseFromCache: EventDefinition<HttpResponse>

Usage Examples:

val client = HttpClient {
    install(HttpCache) {
        publicStorage(CacheStorage.Unlimited())
    }
    
    // Monitor cache performance
    monitor.subscribe(HttpCache.HttpResponseFromCache) { response ->
        println("Cache HIT: ${response.request.url}")
        println("Status: ${response.status}")
        println("Cache headers: ${response.headers[HttpHeaders.CacheControl]}")
    }
}

// Track cache statistics
var cacheHits = 0
var totalRequests = 0

client.monitor.subscribe(HttpRequestCreated) { request ->
    totalRequests++
}

client.monitor.subscribe(HttpCache.HttpResponseFromCache) { response ->
    cacheHits++
    val hitRate = (cacheHits.toFloat() / totalRequests) * 100
    println("Cache hit rate: ${String.format("%.1f%%", hitRate)}")
}

// Make requests to see cache behavior
repeat(5) {
    val response = client.get("https://api.example.com/static-data")
    println("Request $it: ${response.status}")
}

Cache Exceptions

Exception handling for cache-related errors.

/**
 * Exception thrown when cache is in invalid state
 */
class InvalidCacheStateException(message: String) : IllegalStateException(message)

Usage Examples:

try {
    val client = HttpClient {
        install(HttpCache) {
            publicStorage(CustomCacheStorage())
        }
    }
    
    val response = client.get("https://api.example.com/data")
} catch (e: InvalidCacheStateException) {
    println("Cache error: ${e.message}")
    // Handle cache corruption or invalid state
} catch (e: Exception) {
    println("Other error: ${e.message}")
}

Advanced Caching Patterns

Advanced usage patterns for complex caching scenarios.

// Custom cache storage with persistence
class PersistentCacheStorage(private val cacheDir: File) : CacheStorage {
    override suspend fun store(url: Url, data: CachedResponseData) {
        // Store to filesystem or database
    }
    
    override suspend fun find(url: Url, varyKeys: Map<String, String>): CachedResponseData? {
        // Load from persistent storage
        return null
    }
    
    override suspend fun findAll(url: Url): Set<CachedResponseData> {
        // Load all variants from storage
        return emptySet()
    }
}

Usage Examples:

// Multi-tier caching with memory + disk
class TieredCacheStorage(
    private val memoryCache: CacheStorage,
    private val diskCache: CacheStorage
) : CacheStorage {
    
    override suspend fun store(url: Url, data: CachedResponseData) {
        // Store in both memory and disk
        memoryCache.store(url, data)
        diskCache.store(url, data)
    }
    
    override suspend fun find(url: Url, varyKeys: Map<String, String>): CachedResponseData? {
        // Try memory first, then disk
        return memoryCache.find(url, varyKeys)
            ?: diskCache.find(url, varyKeys)?.also {
                // Promote to memory cache
                memoryCache.store(url, it)
            }
    }
    
    override suspend fun findAll(url: Url): Set<CachedResponseData> {
        val memory = memoryCache.findAll(url)
        val disk = diskCache.findAll(url)
        return memory + disk
    }
}

val client = HttpClient {
    install(HttpCache) {
        publicStorage(TieredCacheStorage(
            memoryCache = CacheStorage.Unlimited(),
            diskCache = PersistentCacheStorage(File("cache"))
        ))
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-io-ktor--ktor-client-core-iosx64

docs

client-configuration.md

cookie-management.md

engine-configuration.md

form-handling.md

http-caching.md

index.md

plugin-system.md

request-building.md

response-handling.md

websocket-support.md

tile.json