0
# HTTP Caching
1
2
Response caching with configurable storage backends, cache control support, and HTTP-compliant caching behavior for improved performance.
3
4
## Capabilities
5
6
### HttpCache Plugin
7
8
Core plugin for HTTP response caching with public and private storage options.
9
10
```kotlin { .api }
11
/**
12
* HTTP response caching plugin
13
*/
14
class HttpCache internal constructor(
15
private val publicStorage: CacheStorage,
16
private val privateStorage: CacheStorage,
17
private val isSharedClient: Boolean
18
) {
19
/**
20
* Cache configuration
21
*/
22
class Config {
23
/** Whether client is shared between multiple users */
24
var isShared: Boolean = false
25
26
/**
27
* Configure public cache storage
28
*/
29
fun publicStorage(storage: CacheStorage)
30
31
/**
32
* Configure private cache storage
33
*/
34
fun privateStorage(storage: CacheStorage)
35
}
36
37
companion object : HttpClientPlugin<Config, HttpCache> {
38
override val key: AttributeKey<HttpCache> = AttributeKey("HttpCache")
39
40
/** Event fired when response is served from cache */
41
val HttpResponseFromCache: EventDefinition<HttpResponse>
42
}
43
}
44
```
45
46
**Usage Examples:**
47
48
```kotlin
49
import io.ktor.client.plugins.cache.*
50
51
val client = HttpClient {
52
install(HttpCache) {
53
// Configure as shared client
54
isShared = false
55
56
// Use unlimited cache storage
57
publicStorage(CacheStorage.Unlimited())
58
privateStorage(CacheStorage.Unlimited())
59
}
60
61
// Monitor cache hits
62
monitor.subscribe(HttpCache.HttpResponseFromCache) { response ->
63
println("Cache hit for: ${response.request.url}")
64
}
65
}
66
67
// First request - fetches from server and caches
68
val response1 = client.get("https://api.example.com/data")
69
println("First request: ${response1.status}")
70
71
// Second request - served from cache if cacheable
72
val response2 = client.get("https://api.example.com/data")
73
println("Second request: ${response2.status}")
74
```
75
76
### CacheStorage Interface
77
78
Modern cache storage interface for storing and retrieving cached responses.
79
80
```kotlin { .api }
81
/**
82
* Cache storage interface for HTTP responses
83
*/
84
interface CacheStorage {
85
/**
86
* Store cached response data
87
*/
88
suspend fun store(url: Url, data: CachedResponseData)
89
90
/**
91
* Find cached response with vary key matching
92
*/
93
suspend fun find(url: Url, varyKeys: Map<String, String>): CachedResponseData?
94
95
/**
96
* Find all cached responses for URL
97
*/
98
suspend fun findAll(url: Url): Set<CachedResponseData>
99
100
companion object {
101
/** Create unlimited in-memory cache storage */
102
fun Unlimited(): CacheStorage
103
104
/** Disabled cache storage (no-op) */
105
val Disabled: CacheStorage
106
}
107
}
108
```
109
110
### Built-in Cache Storage Implementations
111
112
Pre-built storage implementations for different caching strategies.
113
114
```kotlin { .api }
115
/**
116
* Unlimited in-memory cache storage
117
*/
118
class UnlimitedStorage : CacheStorage {
119
override suspend fun store(url: Url, data: CachedResponseData)
120
override suspend fun find(url: Url, varyKeys: Map<String, String>): CachedResponseData?
121
override suspend fun findAll(url: Url): Set<CachedResponseData>
122
}
123
124
/**
125
* Disabled cache storage (no-op implementation)
126
*/
127
object DisabledStorage : CacheStorage {
128
override suspend fun store(url: Url, data: CachedResponseData) {}
129
override suspend fun find(url: Url, varyKeys: Map<String, String>): CachedResponseData? = null
130
override suspend fun findAll(url: Url): Set<CachedResponseData> = emptySet()
131
}
132
```
133
134
**Usage Examples:**
135
136
```kotlin
137
// Unlimited caching
138
val unlimitedClient = HttpClient {
139
install(HttpCache) {
140
publicStorage(CacheStorage.Unlimited())
141
privateStorage(CacheStorage.Unlimited())
142
}
143
}
144
145
// Disabled caching
146
val noCacheClient = HttpClient {
147
install(HttpCache) {
148
publicStorage(CacheStorage.Disabled)
149
privateStorage(CacheStorage.Disabled)
150
}
151
}
152
153
// Custom cache storage with size limit
154
class LimitedCacheStorage(private val maxEntries: Int) : CacheStorage {
155
private val cache = LinkedHashMap<String, CachedResponseData>()
156
157
override suspend fun store(url: Url, data: CachedResponseData) {
158
val key = url.toString()
159
if (cache.size >= maxEntries && key !in cache) {
160
// Remove oldest entry
161
cache.remove(cache.keys.first())
162
}
163
cache[key] = data
164
}
165
166
override suspend fun find(url: Url, varyKeys: Map<String, String>): CachedResponseData? {
167
return cache[url.toString()]
168
}
169
170
override suspend fun findAll(url: Url): Set<CachedResponseData> {
171
return cache[url.toString()]?.let { setOf(it) } ?: emptySet()
172
}
173
}
174
175
val limitedClient = HttpClient {
176
install(HttpCache) {
177
publicStorage(LimitedCacheStorage(maxEntries = 100))
178
privateStorage(LimitedCacheStorage(maxEntries = 50))
179
}
180
}
181
```
182
183
### CachedResponseData Class
184
185
Data class representing cached HTTP response with metadata.
186
187
```kotlin { .api }
188
/**
189
* Cached response data with HTTP metadata
190
*/
191
data class CachedResponseData(
192
/** Original request URL */
193
val url: Url,
194
195
/** Response status code */
196
val statusCode: HttpStatusCode,
197
198
/** Request timestamp */
199
val requestTime: GMTDate,
200
201
/** Response timestamp */
202
val responseTime: GMTDate,
203
204
/** HTTP protocol version */
205
val version: HttpProtocolVersion,
206
207
/** Cache expiration time */
208
val expires: GMTDate,
209
210
/** Response headers */
211
val headers: Headers,
212
213
/** Vary header matching keys */
214
val varyKeys: Map<String, String>,
215
216
/** Response body content */
217
val body: ByteArray
218
)
219
```
220
221
**Usage Examples:**
222
223
```kotlin
224
// Create cached response data manually
225
val cachedData = CachedResponseData(
226
url = Url("https://api.example.com/data"),
227
statusCode = HttpStatusCode.OK,
228
requestTime = GMTDate(),
229
responseTime = GMTDate(),
230
version = HttpProtocolVersion.HTTP_1_1,
231
expires = GMTDate() + 3600_000, // 1 hour from now
232
headers = headersOf(
233
HttpHeaders.ContentType, "application/json",
234
HttpHeaders.CacheControl, "public, max-age=3600"
235
),
236
varyKeys = mapOf(
237
"Accept-Language" to "en-US",
238
"Accept-Encoding" to "gzip"
239
),
240
body = """{"message": "cached data"}""".toByteArray()
241
)
242
243
// Store in cache manually
244
val storage = CacheStorage.Unlimited()
245
storage.store(cachedData.url, cachedData)
246
247
// Retrieve from cache
248
val retrieved = storage.find(
249
url = Url("https://api.example.com/data"),
250
varyKeys = mapOf(
251
"Accept-Language" to "en-US",
252
"Accept-Encoding" to "gzip"
253
)
254
)
255
256
if (retrieved != null) {
257
println("Found cached data: ${String(retrieved.body)}")
258
println("Expires: ${retrieved.expires}")
259
}
260
```
261
262
### Cache Control Headers
263
264
Support for HTTP cache control directives and validation.
265
266
```kotlin { .api }
267
/**
268
* Cache storage extensions for response handling
269
*/
270
suspend fun CacheStorage.store(response: HttpResponse): CachedResponseData
271
suspend fun CacheStorage.store(
272
response: HttpResponse,
273
varyKeys: Map<String, String>
274
): CachedResponseData
275
suspend fun CacheStorage.store(
276
response: HttpResponse,
277
varyKeys: Map<String, String>,
278
isShared: Boolean
279
): CachedResponseData
280
```
281
282
**Usage Examples:**
283
284
```kotlin
285
val client = HttpClient {
286
install(HttpCache) {
287
publicStorage(CacheStorage.Unlimited())
288
}
289
}
290
291
// Responses with cache headers are automatically cached
292
val response1 = client.get("https://api.example.com/public-data") {
293
headers {
294
// Request headers that might affect caching
295
append(HttpHeaders.AcceptLanguage, "en-US")
296
append(HttpHeaders.AcceptEncoding, "gzip")
297
}
298
}
299
300
// Check if response was cached
301
val cacheControl = response1.headers[HttpHeaders.CacheControl]
302
println("Cache-Control: $cacheControl")
303
304
// Conditional requests with cache validation
305
val response2 = client.get("https://api.example.com/data") {
306
headers {
307
// Add conditional headers for cache validation
308
append(HttpHeaders.IfModifiedSince, lastModified)
309
append(HttpHeaders.IfNoneMatch, etag)
310
}
311
}
312
313
// Handle 304 Not Modified responses
314
if (response2.status == HttpStatusCode.NotModified) {
315
println("Content not modified, using cached version")
316
}
317
```
318
319
### Cache Events and Monitoring
320
321
Events for monitoring cache behavior and performance.
322
323
```kotlin { .api }
324
/**
325
* Event fired when response is served from cache
326
*/
327
val HttpCache.HttpResponseFromCache: EventDefinition<HttpResponse>
328
```
329
330
**Usage Examples:**
331
332
```kotlin
333
val client = HttpClient {
334
install(HttpCache) {
335
publicStorage(CacheStorage.Unlimited())
336
}
337
338
// Monitor cache performance
339
monitor.subscribe(HttpCache.HttpResponseFromCache) { response ->
340
println("Cache HIT: ${response.request.url}")
341
println("Status: ${response.status}")
342
println("Cache headers: ${response.headers[HttpHeaders.CacheControl]}")
343
}
344
}
345
346
// Track cache statistics
347
var cacheHits = 0
348
var totalRequests = 0
349
350
client.monitor.subscribe(HttpRequestCreated) { request ->
351
totalRequests++
352
}
353
354
client.monitor.subscribe(HttpCache.HttpResponseFromCache) { response ->
355
cacheHits++
356
val hitRate = (cacheHits.toFloat() / totalRequests) * 100
357
println("Cache hit rate: ${String.format("%.1f%%", hitRate)}")
358
}
359
360
// Make requests to see cache behavior
361
repeat(5) {
362
val response = client.get("https://api.example.com/static-data")
363
println("Request $it: ${response.status}")
364
}
365
```
366
367
### Cache Exceptions
368
369
Exception handling for cache-related errors.
370
371
```kotlin { .api }
372
/**
373
* Exception thrown when cache is in invalid state
374
*/
375
class InvalidCacheStateException(message: String) : IllegalStateException(message)
376
```
377
378
**Usage Examples:**
379
380
```kotlin
381
try {
382
val client = HttpClient {
383
install(HttpCache) {
384
publicStorage(CustomCacheStorage())
385
}
386
}
387
388
val response = client.get("https://api.example.com/data")
389
} catch (e: InvalidCacheStateException) {
390
println("Cache error: ${e.message}")
391
// Handle cache corruption or invalid state
392
} catch (e: Exception) {
393
println("Other error: ${e.message}")
394
}
395
```
396
397
### Advanced Caching Patterns
398
399
Advanced usage patterns for complex caching scenarios.
400
401
```kotlin { .api }
402
// Custom cache storage with persistence
403
class PersistentCacheStorage(private val cacheDir: File) : CacheStorage {
404
override suspend fun store(url: Url, data: CachedResponseData) {
405
// Store to filesystem or database
406
}
407
408
override suspend fun find(url: Url, varyKeys: Map<String, String>): CachedResponseData? {
409
// Load from persistent storage
410
return null
411
}
412
413
override suspend fun findAll(url: Url): Set<CachedResponseData> {
414
// Load all variants from storage
415
return emptySet()
416
}
417
}
418
```
419
420
**Usage Examples:**
421
422
```kotlin
423
// Multi-tier caching with memory + disk
424
class TieredCacheStorage(
425
private val memoryCache: CacheStorage,
426
private val diskCache: CacheStorage
427
) : CacheStorage {
428
429
override suspend fun store(url: Url, data: CachedResponseData) {
430
// Store in both memory and disk
431
memoryCache.store(url, data)
432
diskCache.store(url, data)
433
}
434
435
override suspend fun find(url: Url, varyKeys: Map<String, String>): CachedResponseData? {
436
// Try memory first, then disk
437
return memoryCache.find(url, varyKeys)
438
?: diskCache.find(url, varyKeys)?.also {
439
// Promote to memory cache
440
memoryCache.store(url, it)
441
}
442
}
443
444
override suspend fun findAll(url: Url): Set<CachedResponseData> {
445
val memory = memoryCache.findAll(url)
446
val disk = diskCache.findAll(url)
447
return memory + disk
448
}
449
}
450
451
val client = HttpClient {
452
install(HttpCache) {
453
publicStorage(TieredCacheStorage(
454
memoryCache = CacheStorage.Unlimited(),
455
diskCache = PersistentCacheStorage(File("cache"))
456
))
457
}
458
}
459
```