or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdencoders.mdindex.mdrequest-compression.mdresponse-decompression.md

encoders.mddocs/

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

```