0
# Encoders
1
2
Content encoding algorithms for HTTP compression and decompression, including built-in encoders and custom encoder implementation.
3
4
## ContentEncoder Interface
5
6
```kotlin { .api }
7
interface ContentEncoder : Encoder {
8
/**
9
* Encoder identifier to use in http headers.
10
*/
11
val name: String
12
13
/**
14
* Provides an estimation for the compressed length based on the originalLength or return null if it's impossible.
15
*/
16
fun predictCompressedLength(contentLength: Long): Long? = null
17
}
18
19
interface Encoder {
20
/**
21
* Launch coroutine to encode source bytes.
22
*/
23
fun encode(
24
source: ByteReadChannel,
25
coroutineContext: CoroutineContext = EmptyCoroutineContext
26
): ByteReadChannel
27
28
/**
29
* Launch coroutine to encode source bytes.
30
*/
31
fun encode(
32
source: ByteWriteChannel,
33
coroutineContext: CoroutineContext = EmptyCoroutineContext
34
): ByteWriteChannel
35
36
/**
37
* Launch coroutine to decode source bytes.
38
*/
39
fun decode(
40
source: ByteReadChannel,
41
coroutineContext: CoroutineContext = EmptyCoroutineContext
42
): ByteReadChannel
43
}
44
```
45
46
## Built-in Encoders
47
48
### GZipEncoder
49
50
GZIP compression algorithm implementation with cross-platform support.
51
52
```kotlin { .api }
53
object GZipEncoder : ContentEncoder {
54
override val name: String // "gzip"
55
56
override fun encode(
57
source: ByteReadChannel,
58
coroutineContext: CoroutineContext
59
): ByteReadChannel
60
61
override fun encode(
62
source: ByteWriteChannel,
63
coroutineContext: CoroutineContext
64
): ByteWriteChannel
65
66
override fun decode(
67
source: ByteReadChannel,
68
coroutineContext: CoroutineContext
69
): ByteReadChannel
70
}
71
```
72
73
**Usage:**
74
```kotlin
75
import io.ktor.util.*
76
77
install(ContentEncoding) {
78
gzip() // Enable GZIP compression
79
gzip(0.9f) // Enable with specific quality value
80
}
81
82
// Manual encoder usage
83
val compressed = GZipEncoder.encode(originalData, coroutineContext)
84
val decompressed = GZipEncoder.decode(compressedData, coroutineContext)
85
```
86
87
**Characteristics:**
88
- Header: `Content-Encoding: gzip`
89
- Good compression ratio
90
- Widely supported
91
- Standard web compression algorithm
92
- Platform-specific implementations
93
94
### DeflateEncoder
95
96
Deflate compression algorithm implementation with cross-platform support.
97
98
```kotlin { .api }
99
object DeflateEncoder : ContentEncoder {
100
override val name: String // "deflate"
101
102
override fun encode(
103
source: ByteReadChannel,
104
coroutineContext: CoroutineContext
105
): ByteReadChannel
106
107
override fun encode(
108
source: ByteWriteChannel,
109
coroutineContext: CoroutineContext
110
): ByteWriteChannel
111
112
override fun decode(
113
source: ByteReadChannel,
114
coroutineContext: CoroutineContext
115
): ByteReadChannel
116
}
117
```
118
119
**Usage:**
120
```kotlin
121
import io.ktor.util.*
122
123
install(ContentEncoding) {
124
deflate() // Enable Deflate compression
125
deflate(0.8f) // Enable with specific quality value
126
}
127
128
// Manual encoder usage
129
val compressed = DeflateEncoder.encode(originalData, coroutineContext)
130
val decompressed = DeflateEncoder.decode(compressedData, coroutineContext)
131
```
132
133
**Characteristics:**
134
- Header: `Content-Encoding: deflate`
135
- Fast compression/decompression
136
- Good compression ratio
137
- Less common than gzip
138
- Platform-specific implementations
139
140
### IdentityEncoder
141
142
Identity (no-op) encoder that passes data through unchanged.
143
144
```kotlin { .api }
145
object IdentityEncoder : ContentEncoder {
146
override val name: String // "identity"
147
148
override fun encode(
149
source: ByteReadChannel,
150
coroutineContext: CoroutineContext
151
): ByteReadChannel
152
153
override fun encode(
154
source: ByteWriteChannel,
155
coroutineContext: CoroutineContext
156
): ByteWriteChannel
157
158
override fun decode(
159
source: ByteReadChannel,
160
coroutineContext: CoroutineContext
161
): ByteReadChannel
162
163
override fun predictCompressedLength(contentLength: Long): Long
164
}
165
```
166
167
**Usage:**
168
```kotlin
169
install(ContentEncoding) {
170
identity() // Enable identity encoding
171
identity(0.1f) // Low priority fallback
172
}
173
174
// Manual encoder usage (passes data through unchanged)
175
val unchanged = IdentityEncoder.encode(data, coroutineContext)
176
val alsounchanged = IdentityEncoder.decode(data, coroutineContext)
177
```
178
179
**Characteristics:**
180
- Header: `Content-Encoding: identity`
181
- No compression applied
182
- Data passed through unchanged
183
- Used as fallback option
184
- Perfect length prediction (returns original length)
185
186
## Custom Encoder Implementation
187
188
### Basic Custom Encoder
189
190
```kotlin
191
class CustomEncoder(private val algorithmName: String) : ContentEncoder {
192
override val name: String = algorithmName
193
194
override fun encode(
195
source: ByteReadChannel,
196
coroutineContext: CoroutineContext
197
): ByteReadChannel {
198
// Implement compression algorithm
199
return source // Placeholder - implement actual compression
200
}
201
202
override fun encode(
203
source: ByteWriteChannel,
204
coroutineContext: CoroutineContext
205
): ByteWriteChannel {
206
// Implement compression for write channel
207
return source // Placeholder - implement actual compression
208
}
209
210
override fun decode(
211
source: ByteReadChannel,
212
coroutineContext: CoroutineContext
213
): ByteReadChannel {
214
// Implement decompression algorithm
215
return source // Placeholder - implement actual decompression
216
}
217
218
override fun predictCompressedLength(contentLength: Long): Long? {
219
// Return estimated compressed size or null if unknown
220
return (contentLength * 0.6).toLong() // Example: 40% compression
221
}
222
}
223
```
224
225
### Advanced Custom Encoder with Coroutines
226
227
```kotlin
228
import kotlinx.coroutines.*
229
import io.ktor.utils.io.*
230
231
class AsyncCompressionEncoder : ContentEncoder {
232
override val name: String = "async-compress"
233
234
override fun encode(
235
source: ByteReadChannel,
236
coroutineContext: CoroutineContext
237
): ByteReadChannel {
238
return GlobalScope.produce(coroutineContext) {
239
val buffer = ByteArray(8192)
240
while (!source.isClosedForRead) {
241
val read = source.readAvailable(buffer)
242
if (read > 0) {
243
// Apply compression algorithm
244
val compressed = compressBytes(buffer, 0, read)
245
channel.writeFully(compressed)
246
}
247
}
248
}.channel
249
}
250
251
override fun encode(
252
source: ByteWriteChannel,
253
coroutineContext: CoroutineContext
254
): ByteWriteChannel {
255
return writer(coroutineContext) {
256
// Implement write channel compression
257
source.close()
258
}.channel
259
}
260
261
override fun decode(
262
source: ByteReadChannel,
263
coroutineContext: CoroutineContext
264
): ByteReadChannel {
265
return GlobalScope.produce(coroutineContext) {
266
val buffer = ByteArray(8192)
267
while (!source.isClosedForRead) {
268
val read = source.readAvailable(buffer)
269
if (read > 0) {
270
// Apply decompression algorithm
271
val decompressed = decompressBytes(buffer, 0, read)
272
channel.writeFully(decompressed)
273
}
274
}
275
}.channel
276
}
277
278
private fun compressBytes(data: ByteArray, offset: Int, length: Int): ByteArray {
279
// Implement compression logic
280
return data.copyOfRange(offset, offset + length)
281
}
282
283
private fun decompressBytes(data: ByteArray, offset: Int, length: Int): ByteArray {
284
// Implement decompression logic
285
return data.copyOfRange(offset, offset + length)
286
}
287
}
288
```
289
290
### Custom Encoder Registration
291
292
```kotlin
293
val customEncoder = CustomEncoder("my-algorithm")
294
295
val client = HttpClient {
296
install(ContentEncoding) {
297
customEncoder(customEncoder, 0.9f)
298
gzip(0.8f) // Fallback to gzip
299
identity() // Final fallback
300
}
301
}
302
```
303
304
## Encoder Selection and Priority
305
306
### Quality Value Ordering
307
308
```kotlin
309
install(ContentEncoding) {
310
gzip(1.0f) // Highest priority
311
deflate(0.8f) // Medium priority
312
identity(0.1f) // Lowest priority
313
}
314
315
// Generates: Accept-Encoding: gzip;q=1.0,deflate;q=0.8,identity;q=0.1
316
```
317
318
### Server Response Processing
319
320
```kotlin
321
// Server responds with: Content-Encoding: gzip, deflate
322
val response = client.get("/compressed-data")
323
324
// Decompression applied in reverse order:
325
// 1. deflate decoder applied first
326
// 2. gzip decoder applied second
327
// 3. Original content returned
328
329
val decoders = response.appliedDecoders
330
println(decoders) // ["deflate", "gzip"]
331
```
332
333
### Multi-Layer Encoding Support
334
335
```kotlin
336
// Request with multiple encodings
337
client.post("/upload") {
338
compress("gzip", "deflate") // Apply deflate, then gzip
339
setBody(data)
340
}
341
342
// Server processes in order: gzip decompression, then deflate decompression
343
```
344
345
## Platform-Specific Implementations
346
347
### JVM Implementation
348
349
```kotlin
350
// JVM uses java.util.zip implementations
351
actual object GZipEncoder : ContentEncoder, Encoder by GZip {
352
actual override val name: String = "gzip"
353
}
354
355
actual object DeflateEncoder : ContentEncoder, Encoder by Deflate {
356
actual override val name: String = "deflate"
357
}
358
```
359
360
### Native Implementation
361
362
```kotlin
363
// Native platforms use platform-specific compression libraries
364
expect object GZipEncoder : ContentEncoder {
365
override val name: String
366
// Platform-specific implementation
367
}
368
```
369
370
### JavaScript Implementation
371
372
```kotlin
373
// JavaScript uses browser or Node.js compression APIs
374
expect object GZipEncoder : ContentEncoder {
375
override val name: String
376
// Browser/Node.js specific implementation
377
}
378
```
379
380
## Error Handling
381
382
### Unsupported Encodings
383
384
```kotlin { .api }
385
class UnsupportedContentEncodingException(encoding: String) :
386
IllegalStateException("Content-Encoding: $encoding unsupported.")
387
```
388
389
**Error Scenarios:**
390
```kotlin
391
try {
392
val response = client.get("/data")
393
val content = response.body<String>()
394
} catch (e: UnsupportedContentEncodingException) {
395
println("Server used unsupported encoding: ${e.message}")
396
// Handle unknown encoding
397
}
398
```
399
400
### Encoder Validation
401
402
```kotlin
403
class InvalidEncoder : ContentEncoder {
404
override val name: String = "" // Invalid: empty name
405
}
406
407
install(ContentEncoding) {
408
try {
409
customEncoder(InvalidEncoder())
410
} catch (e: IllegalArgumentException) {
411
println("Invalid encoder name: ${e.message}")
412
}
413
}
414
```
415
416
## Performance Considerations
417
418
### Compression Level vs Speed
419
420
```kotlin
421
// High compression, slower
422
install(ContentEncoding) {
423
gzip(1.0f) // Prefer best compression
424
deflate(0.5f) // Lower priority
425
}
426
427
// Faster compression, lower ratio
428
install(ContentEncoding) {
429
deflate(1.0f) // Prefer faster algorithm
430
gzip(0.5f) // Lower priority
431
}
432
```
433
434
### Memory Usage
435
436
```kotlin
437
class MemoryEfficientEncoder : ContentEncoder {
438
override val name: String = "memory-efficient"
439
440
override fun encode(
441
source: ByteReadChannel,
442
coroutineContext: CoroutineContext
443
): ByteReadChannel {
444
// Use streaming compression to minimize memory usage
445
return compressStream(source, coroutineContext)
446
}
447
448
private fun compressStream(
449
source: ByteReadChannel,
450
context: CoroutineContext
451
): ByteReadChannel {
452
// Implement streaming compression
453
TODO("Implement streaming compression")
454
}
455
}
456
```
457
458
## Complete Example
459
460
```kotlin
461
import io.ktor.client.*
462
import io.ktor.client.plugins.compression.*
463
import io.ktor.util.*
464
465
// Custom high-performance encoder
466
class FastEncoder : ContentEncoder {
467
override val name: String = "fast"
468
469
override fun encode(source: ByteReadChannel, coroutineContext: CoroutineContext): ByteReadChannel {
470
// Fast compression implementation
471
return source // Placeholder
472
}
473
474
override fun encode(source: ByteWriteChannel, coroutineContext: CoroutineContext): ByteWriteChannel {
475
return source // Placeholder
476
}
477
478
override fun decode(source: ByteReadChannel, coroutineContext: CoroutineContext): ByteReadChannel {
479
return source // Placeholder
480
}
481
482
override fun predictCompressedLength(contentLength: Long): Long = contentLength / 2
483
}
484
485
suspend fun main() {
486
val client = HttpClient {
487
install(ContentEncoding) {
488
mode = ContentEncodingConfig.Mode.All
489
490
// Custom encoder with highest priority
491
customEncoder(FastEncoder(), 1.0f)
492
493
// Standard encoders as fallbacks
494
gzip(0.9f)
495
deflate(0.8f)
496
identity(0.1f)
497
}
498
}
499
500
// Upload with custom compression
501
val uploadResponse = client.post("https://api.example.com/upload") {
502
compress("fast", "gzip")
503
setBody("Large data payload")
504
}
505
506
// Download with automatic decompression
507
val downloadResponse = client.get("https://api.example.com/download")
508
val content = downloadResponse.body<String>()
509
510
println("Applied decoders: ${downloadResponse.appliedDecoders}")
511
512
client.close()
513
}
514
```