0
# Response Decompression
1
2
Automatically decompress incoming HTTP response bodies based on Content-Encoding headers with transparent processing and multi-layer decompression support.
3
4
## Response Decompression API
5
6
```kotlin { .api }
7
/**
8
* List of ContentEncoder names that were used to decode response body.
9
*/
10
val HttpResponse.appliedDecoders: List<String>
11
```
12
13
## Automatic Response Decompression
14
15
### Basic Decompression
16
17
```kotlin
18
import io.ktor.client.*
19
import io.ktor.client.plugins.compression.*
20
import io.ktor.client.statement.*
21
22
val client = HttpClient {
23
install(ContentEncoding) {
24
// Default mode: DecompressResponse
25
gzip()
26
deflate()
27
identity()
28
}
29
}
30
31
// Automatic decompression based on Content-Encoding header
32
val response = client.get("https://api.example.com/data")
33
val content = response.body<String>() // Automatically decompressed
34
35
// Check which decoders were applied
36
val usedDecoders = response.appliedDecoders
37
println("Applied decoders: $usedDecoders") // e.g., ["gzip"]
38
```
39
40
### Accept-Encoding Header Management
41
42
```kotlin
43
// Client automatically sets Accept-Encoding header
44
val response = client.get("/compressed-endpoint")
45
46
// Request includes: Accept-Encoding: gzip,deflate,identity
47
// Server responds with: Content-Encoding: gzip
48
// Response body is automatically decompressed
49
```
50
51
## Multi-Layer Decompression
52
53
### Decompression Order
54
55
```kotlin
56
// Server response with: Content-Encoding: gzip, deflate
57
val response = client.get("/multi-compressed")
58
59
// Decompression applied in reverse order:
60
// 1. gzip decompression applied first
61
// 2. deflate decompression applied second
62
// 3. Original content returned
63
64
val decoders = response.appliedDecoders
65
println(decoders) // ["gzip", "deflate"] - in application order
66
```
67
68
### Complex Encoding Chains
69
70
```kotlin
71
// Server applies: deflate → gzip → brotli (if supported)
72
// Response header: Content-Encoding: deflate, gzip, br
73
74
val response = client.get("/complex-compression")
75
val content = response.body<String>()
76
77
// Decompression chain: br → gzip → deflate (reverse order)
78
val appliedDecoders = response.appliedDecoders
79
println("Decompression chain: $appliedDecoders")
80
```
81
82
## Decompression Modes
83
84
### DecompressResponse Mode (Default)
85
86
```kotlin
87
val client = HttpClient {
88
install(ContentEncoding) {
89
mode = ContentEncodingConfig.Mode.DecompressResponse
90
gzip()
91
deflate()
92
}
93
}
94
95
// Only decompresses responses, never compresses requests
96
val response = client.get("/data")
97
val decompressed = response.body<String>()
98
```
99
100
### All Mode (Bidirectional)
101
102
```kotlin
103
val client = HttpClient {
104
install(ContentEncoding) {
105
mode = ContentEncodingConfig.Mode.All
106
gzip()
107
deflate()
108
}
109
}
110
111
// Both compresses requests AND decompresses responses
112
val response = client.post("/upload") {
113
compress("gzip") // Request compression available
114
setBody(data)
115
}
116
val result = response.body<String>() // Response decompression automatic
117
```
118
119
### CompressRequest Mode
120
121
```kotlin
122
val client = HttpClient {
123
install(ContentEncoding) {
124
mode = ContentEncodingConfig.Mode.CompressRequest
125
gzip()
126
}
127
}
128
129
// Only compresses requests, does NOT decompress responses
130
val response = client.get("/data")
131
// Response is NOT automatically decompressed even if Content-Encoding header present
132
```
133
134
## Response Processing Pipeline
135
136
### Header Processing
137
138
```kotlin
139
// Response headers before decompression:
140
// Content-Encoding: gzip
141
// Content-Length: 1234 (compressed size)
142
143
val response = client.get("/compressed")
144
145
// Response headers after decompression:
146
// Content-Encoding: (removed)
147
// Content-Length: (removed - unknown after decompression)
148
// Other headers preserved
149
150
val content = response.body<String>() // Decompressed content
151
```
152
153
### Content-Length Handling
154
155
```kotlin
156
val response = client.get("/gzipped-content")
157
158
// Original compressed length (if available)
159
val originalLength = response.headers[HttpHeaders.ContentLength]?.toLong()
160
161
// Content-Length header removed after decompression
162
val finalLength = response.headers[HttpHeaders.ContentLength] // null
163
164
// Actual decompressed content
165
val content = response.body<String>()
166
val actualLength = content.length
167
```
168
169
## Error Handling
170
171
### Unsupported Encodings
172
173
```kotlin { .api }
174
class UnsupportedContentEncodingException(encoding: String) :
175
IllegalStateException("Content-Encoding: $encoding unsupported.")
176
```
177
178
**Handling Unknown Encodings:**
179
```kotlin
180
try {
181
val response = client.get("/unknown-encoding")
182
val content = response.body<String>()
183
} catch (e: UnsupportedContentEncodingException) {
184
println("Server used unsupported encoding: ${e.message}")
185
186
// Fallback: handle raw compressed content
187
val rawContent = response.body<ByteArray>()
188
// Custom decompression logic here
189
}
190
```
191
192
### Partial Encoding Support
193
194
```kotlin
195
val client = HttpClient {
196
install(ContentEncoding) {
197
gzip() // Supported
198
deflate() // Supported
199
// brotli not configured - will cause UnsupportedContentEncodingException
200
}
201
}
202
203
// Server responds with: Content-Encoding: br, gzip
204
try {
205
val response = client.get("/brotli-compressed")
206
val content = response.body<String>()
207
} catch (e: UnsupportedContentEncodingException) {
208
println("Brotli encoding not supported")
209
}
210
```
211
212
### Decompression Errors
213
214
```kotlin
215
suspend fun robustDownload(url: String): String? {
216
return try {
217
val response = client.get(url)
218
response.body<String>()
219
} catch (e: UnsupportedContentEncodingException) {
220
println("Encoding not supported: ${e.message}")
221
null
222
} catch (e: Exception) {
223
println("Decompression failed: ${e.message}")
224
null
225
}
226
}
227
```
228
229
## Advanced Response Decompression
230
231
### Content Type Awareness
232
233
```kotlin
234
suspend fun downloadWithContentTypeHandling(url: String) {
235
val response = client.get(url)
236
val contentType = response.contentType()
237
238
when (contentType?.contentType) {
239
"text" -> {
240
val text = response.body<String>()
241
println("Decompressed text: ${text.take(100)}...")
242
}
243
"application/json" -> {
244
val json = response.body<JsonObject>()
245
println("Decompressed JSON keys: ${json.keys}")
246
}
247
"application/octet-stream" -> {
248
val bytes = response.body<ByteArray>()
249
println("Decompressed binary data: ${bytes.size} bytes")
250
}
251
}
252
253
println("Applied decoders: ${response.appliedDecoders}")
254
}
255
```
256
257
### Streaming Decompression
258
259
```kotlin
260
import io.ktor.utils.io.*
261
262
suspend fun streamingDecompression(url: String) {
263
val response = client.get(url)
264
val channel = response.body<ByteReadChannel>()
265
266
// Content is decompressed as it's read from the channel
267
val buffer = ByteArray(8192)
268
while (!channel.isClosedForRead) {
269
val read = channel.readAvailable(buffer)
270
if (read > 0) {
271
// Process decompressed data chunk
272
processChunk(buffer, 0, read)
273
}
274
}
275
276
println("Streaming decompression used: ${response.appliedDecoders}")
277
}
278
279
fun processChunk(data: ByteArray, offset: Int, length: Int) {
280
// Process decompressed data chunk
281
println("Processed ${length} decompressed bytes")
282
}
283
```
284
285
### Conditional Decompression
286
287
```kotlin
288
suspend fun conditionalDecompression(url: String, autoDecompress: Boolean) {
289
val client = if (autoDecompress) {
290
HttpClient {
291
install(ContentEncoding) {
292
gzip()
293
deflate()
294
}
295
}
296
} else {
297
HttpClient() // No content encoding plugin
298
}
299
300
val response = client.get(url)
301
302
if (autoDecompress) {
303
val content = response.body<String>() // Automatically decompressed
304
println("Auto-decompressed content: ${content.take(100)}")
305
} else {
306
val rawContent = response.body<ByteArray>() // Raw compressed content
307
println("Raw content size: ${rawContent.size} bytes")
308
309
// Manual decompression if needed
310
val encoding = response.headers[HttpHeaders.ContentEncoding]
311
if (encoding == "gzip") {
312
val decompressed = manualGzipDecompression(rawContent)
313
println("Manually decompressed: ${decompressed.take(100)}")
314
}
315
}
316
}
317
318
fun manualGzipDecompression(compressed: ByteArray): String {
319
// Implement manual gzip decompression
320
return "Manually decompressed content"
321
}
322
```
323
324
## Response Validation
325
326
### Decoder Chain Validation
327
328
```kotlin
329
suspend fun validateDecompression(url: String, expectedEncodings: List<String>) {
330
val response = client.get(url)
331
val content = response.body<String>()
332
val appliedDecoders = response.appliedDecoders
333
334
if (appliedDecoders == expectedEncodings) {
335
println("✓ Expected decompression chain applied: $appliedDecoders")
336
} else {
337
println("✗ Unexpected decompression chain:")
338
println(" Expected: $expectedEncodings")
339
println(" Actual: $appliedDecoders")
340
}
341
}
342
```
343
344
### Content Integrity Verification
345
346
```kotlin
347
suspend fun verifyDecompressedContent(url: String, expectedChecksum: String) {
348
val response = client.get(url)
349
val content = response.body<ByteArray>()
350
val actualChecksum = content.sha256()
351
352
if (actualChecksum == expectedChecksum) {
353
println("✓ Content integrity verified after decompression")
354
println(" Decoders used: ${response.appliedDecoders}")
355
} else {
356
println("✗ Content integrity check failed")
357
println(" Expected: $expectedChecksum")
358
println(" Actual: $actualChecksum")
359
}
360
}
361
362
fun ByteArray.sha256(): String {
363
// Implement SHA-256 checksum calculation
364
return "calculated-checksum"
365
}
366
```
367
368
## Performance Optimization
369
370
### Decoder Selection Strategy
371
372
```kotlin
373
val client = HttpClient {
374
install(ContentEncoding) {
375
// Optimize for decompression speed
376
deflate(1.0f) // Faster decompression
377
gzip(0.8f) // Slower but better compression
378
identity(0.1f) // Fallback
379
}
380
}
381
```
382
383
### Memory-Efficient Decompression
384
385
```kotlin
386
suspend fun memoryEfficientDownload(url: String): String {
387
val response = client.get(url)
388
389
// Stream-based decompression to minimize memory usage
390
val channel = response.body<ByteReadChannel>()
391
val result = StringBuilder()
392
val buffer = ByteArray(4096)
393
394
while (!channel.isClosedForRead) {
395
val read = channel.readAvailable(buffer)
396
if (read > 0) {
397
result.append(String(buffer, 0, read))
398
}
399
}
400
401
println("Memory-efficient decompression: ${response.appliedDecoders}")
402
return result.toString()
403
}
404
```
405
406
## Integration Examples
407
408
### API Client with Response Validation
409
410
```kotlin
411
class ApiClient(private val client: HttpClient) {
412
413
suspend fun fetchData(endpoint: String): ApiResponse? {
414
return try {
415
val response = client.get(endpoint)
416
val data = response.body<ApiResponse>()
417
418
// Log decompression info
419
if (response.appliedDecoders.isNotEmpty()) {
420
println("Response decompressed with: ${response.appliedDecoders}")
421
}
422
423
data
424
} catch (e: UnsupportedContentEncodingException) {
425
println("Unsupported encoding: ${e.message}")
426
null
427
}
428
}
429
}
430
431
data class ApiResponse(val data: String, val status: String)
432
```
433
434
### File Download Service
435
436
```kotlin
437
class DownloadService(private val client: HttpClient) {
438
439
suspend fun downloadFile(url: String): ByteArray? {
440
return try {
441
val response = client.get(url)
442
val content = response.body<ByteArray>()
443
444
println("Downloaded ${content.size} bytes")
445
if (response.appliedDecoders.isNotEmpty()) {
446
println("Decompressed using: ${response.appliedDecoders}")
447
}
448
449
content
450
} catch (e: Exception) {
451
println("Download failed: ${e.message}")
452
null
453
}
454
}
455
456
suspend fun downloadText(url: String): String? {
457
return try {
458
val response = client.get(url)
459
val text = response.body<String>()
460
461
val decoders = response.appliedDecoders
462
if (decoders.isNotEmpty()) {
463
println("Text decompressed with: $decoders")
464
}
465
466
text
467
} catch (e: Exception) {
468
println("Text download failed: ${e.message}")
469
null
470
}
471
}
472
}
473
```
474
475
## Complete Example
476
477
```kotlin
478
import io.ktor.client.*
479
import io.ktor.client.plugins.compression.*
480
import io.ktor.client.request.*
481
import io.ktor.client.statement.*
482
483
suspend fun main() {
484
val client = HttpClient {
485
install(ContentEncoding) {
486
mode = ContentEncodingConfig.Mode.DecompressResponse
487
gzip()
488
deflate()
489
identity()
490
}
491
}
492
493
// Simple automatic decompression
494
val response1 = client.get("https://httpbin.org/gzip")
495
val content1 = response1.body<String>()
496
println("Gzip decompressed content: ${content1.take(100)}")
497
println("Applied decoders: ${response1.appliedDecoders}")
498
499
// Multi-layer decompression
500
val response2 = client.get("https://example.com/multi-compressed")
501
val content2 = response2.body<String>()
502
println("Multi-layer decompressed: ${content2.take(100)}")
503
println("Decoder chain: ${response2.appliedDecoders}")
504
505
// Error handling for unsupported encodings
506
try {
507
val response3 = client.get("https://example.com/brotli-compressed")
508
val content3 = response3.body<String>()
509
println("Content: $content3")
510
} catch (e: UnsupportedContentEncodingException) {
511
println("Unsupported encoding: ${e.message}")
512
}
513
514
// Streaming decompression for large files
515
val response4 = client.get("https://example.com/large-compressed-file")
516
val channel = response4.body<ByteReadChannel>()
517
var totalBytes = 0
518
val buffer = ByteArray(8192)
519
520
while (!channel.isClosedForRead) {
521
val read = channel.readAvailable(buffer)
522
if (read > 0) {
523
totalBytes += read
524
// Process chunk of decompressed data
525
}
526
}
527
528
println("Streamed $totalBytes decompressed bytes")
529
println("Streaming decoders: ${response4.appliedDecoders}")
530
531
client.close()
532
}
533
```