CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-ktor--ktor-client-encoding

HTTP client content encoding plugin for compression and decompression in Ktor applications.

Pending
Overview
Eval results
Files

request-compression.mddocs/

Request Compression

Compress outgoing HTTP request bodies to reduce bandwidth usage and improve upload performance using configurable compression algorithms.

Request Compression API

/**
 * Compresses request body using ContentEncoding plugin.
 * @param contentEncoderName names of compression encoders to use, such as "gzip", "deflate", etc
 */
fun HttpRequestBuilder.compress(vararg contentEncoderName: String): Unit

/**
 * Compress request body using ContentEncoding plugin.
 * @param contentEncoderNames names of compression encoders to use, such as "gzip", "deflate", etc
 */
fun HttpRequestBuilder.compress(contentEncoderNames: List<String>): Unit

Basic Request Compression

Per-Request Compression

import io.ktor.client.request.*
import io.ktor.client.plugins.compression.*

// Single encoder
client.post("/api/upload") {
    compress("gzip")
    setBody("Large payload data")
}

// Multiple encoders (applied in order)
client.put("/api/update") {
    compress("gzip", "deflate")
    setBody(largeData)
}

// List-based specification
client.patch("/api/modify") {
    compress(listOf("deflate", "gzip"))
    setBody(payload)
}

Global Request Compression Mode

val client = HttpClient {
    install(ContentEncoding) {
        mode = ContentEncodingConfig.Mode.CompressRequest  // Only compress requests
        gzip()
        deflate()
    }
}

// All requests can now use compression
client.post("/upload1") {
    compress("gzip")
    setBody(data1)
}

client.post("/upload2") {
    compress("deflate")
    setBody(data2)
}

Bidirectional Mode

val client = HttpClient {
    install(ContentEncoding) {
        mode = ContentEncodingConfig.Mode.All  // Compress requests AND decompress responses
        gzip()
        deflate()
        identity()
    }
}

client.post("/api/data") {
    compress("gzip")
    setBody(requestData)
}
// Response automatically decompressed if server sends compressed data

Compression Behavior

Header Management

client.post("/upload") {
    compress("gzip")
    setBody("Data to compress")
}

// Automatically sets: Content-Encoding: gzip
// Original Content-Length header is removed (compressed length is different)
// Vary header includes Accept-Encoding

Multi-Layer Compression

// Apply multiple compression layers
client.post("/complex-upload") {
    compress("deflate", "gzip")  // deflate first, then gzip
    setBody(complexData)
}

// Server must decompress in reverse order: gzip first, then deflate

Processing Order:

  1. Original content → deflate compression → intermediate result
  2. Intermediate result → gzip compression → final compressed content
  3. Headers: Content-Encoding: deflate, gzip

Compression with Content Types

import io.ktor.http.*

// JSON data compression
client.post("/api/json") {
    compress("gzip")
    contentType(ContentType.Application.Json)
    setBody("""{"large": "json", "data": "here"}""")
}

// Binary data compression
client.post("/api/binary") {
    compress("deflate")
    contentType(ContentType.Application.OctetStream)
    setBody(binaryData)
}

// Form data compression
client.post("/api/form") {
    compress("gzip")
    contentType(ContentType.Application.FormUrlEncoded)
    setBody(formParameters)
}

Advanced Request Compression

Conditional Compression

suspend fun uploadWithConditionalCompression(data: ByteArray) {
    val shouldCompress = data.size > 1024  // Only compress large payloads
    
    client.post("/upload") {
        if (shouldCompress) {
            compress("gzip")
        }
        setBody(data)
    }
}

Compression Strategy by Content Type

suspend fun uploadWithSmartCompression(content: Any, contentType: ContentType) {
    client.post("/smart-upload") {
        when (contentType) {
            ContentType.Application.Json,
            ContentType.Text.Plain,
            ContentType.Application.Xml -> {
                compress("gzip")  // Text-based content compresses well
            }
            ContentType.Image.JPEG,
            ContentType.Image.PNG -> {
                // Images are already compressed, skip compression
            }
            else -> {
                compress("deflate")  // Default to deflate for other types
            }
        }
        contentType(contentType)
        setBody(content)
    }
}

Large File Upload with Compression

import io.ktor.client.request.forms.*
import io.ktor.http.content.*

suspend fun uploadLargeFile(file: ByteArray) {
    client.post("/large-upload") {
        compress("gzip")
        setBody(MultiPartFormDataContent(
            formData {
                append("file", file, Headers.build {
                    append(HttpHeaders.ContentDisposition, "filename=large-file.dat")
                })
            }
        ))
    }
}

Pipeline Integration

Request Processing Pipeline

// Compression occurs in the request pipeline after content rendering
// Order: Content Creation → Rendering → Compression → Network Send

client.post("/data") {
    // 1. Content created
    setBody(originalContent)
    
    // 2. Content rendered to OutgoingContent
    // 3. Compression applied (if compress() called)
    compress("gzip")
    
    // 4. Compressed content sent over network
}

Compression Attributes

internal val CompressionListAttribute: AttributeKey<List<String>>

Internal Usage:

// The compress() function sets internal attributes
client.post("/upload") {
    compress("gzip", "deflate")
    // Internally: attributes.put(CompressionListAttribute, listOf("gzip", "deflate"))
    setBody(data)
}

Error Handling

Unsupported Encoder Names

try {
    client.post("/upload") {
        compress("unknown-algorithm")  // Not registered in ContentEncodingConfig
        setBody(data)
    }
} catch (e: UnsupportedContentEncodingException) {
    println("Compression failed: ${e.message}")
    // Fallback to uncompressed upload
    client.post("/upload") {
        setBody(data)
    }
}

Compression Failure Handling

suspend fun safeCompressedUpload(data: ByteArray) {
    try {
        // Attempt compressed upload
        client.post("/upload") {
            compress("gzip")
            setBody(data)
        }
    } catch (e: Exception) {
        println("Compressed upload failed: ${e.message}")
        
        // Retry without compression
        client.post("/upload") {
            setBody(data)
        }
    }
}

Performance Considerations

Compression Threshold

suspend fun efficientUpload(data: ByteArray) {
    val compressionThreshold = 1024  // Don't compress small payloads
    
    client.post("/upload") {
        if (data.size >= compressionThreshold) {
            compress("gzip")
        }
        setBody(data)
    }
}

Encoder Selection for Performance

// Fast compression for real-time uploads
client.post("/realtime-data") {
    compress("deflate")  // Generally faster than gzip
    setBody(realtimeData)
}

// Best compression for large uploads
client.post("/large-file") {
    compress("gzip")     // Better compression ratio
    setBody(largeFile)
}

Memory-Efficient Streaming

import io.ktor.utils.io.*

suspend fun streamingCompressedUpload(dataChannel: ByteReadChannel) {
    client.post("/streaming-upload") {
        compress("gzip")
        setBody(object : OutgoingContent.ReadChannelContent() {
            override fun readFrom(): ByteReadChannel = dataChannel
            override val contentLength: Long? = null  // Unknown length for streaming
        })
    }
}

Integration Examples

File Upload Service

class FileUploadService(private val client: HttpClient) {
    
    suspend fun uploadFile(filename: String, content: ByteArray): Boolean {
        return try {
            val response = client.post("/files/upload") {
                compress("gzip")
                contentType(ContentType.Application.OctetStream)
                header("X-Filename", filename)
                setBody(content)
            }
            response.status.isSuccess()
        } catch (e: Exception) {
            false
        }
    }
    
    suspend fun uploadJson(data: Any): Boolean {
        return try {
            val response = client.post("/api/data") {
                compress("gzip")
                contentType(ContentType.Application.Json)
                setBody(data)
            }
            response.status.isSuccess()
        } catch (e: Exception) {
            false
        }
    }
}

Batch Data Upload

suspend fun uploadBatchData(records: List<DataRecord>) {
    val jsonData = Json.encodeToString(records)
    
    client.post("/batch-upload") {
        compress("gzip")  // JSON compresses very well
        contentType(ContentType.Application.Json)
        setBody(jsonData)
    }
}

API Client with Smart Compression

class ApiClient(private val client: HttpClient) {
    
    suspend fun createResource(resource: Resource): Resource {
        val response = client.post("/resources") {
            // Compress JSON payloads
            compress("gzip")
            contentType(ContentType.Application.Json)
            setBody(resource)
        }
        return response.body<Resource>()
    }
    
    suspend fun uploadDocument(document: ByteArray, mimeType: ContentType): String {
        val response = client.post("/documents") {
            // Compress based on content type
            when (mimeType.contentType) {
                "text", "application" -> compress("gzip")
                else -> {
                    // Don't compress binary formats that are already compressed
                }
            }
            contentType(mimeType)
            setBody(document)
        }
        return response.body<String>()
    }
}

Complete Example

import io.ktor.client.*
import io.ktor.client.plugins.compression.*
import io.ktor.client.request.*
import io.ktor.http.*

suspend fun main() {
    val client = HttpClient {
        install(ContentEncoding) {
            mode = ContentEncodingConfig.Mode.All
            gzip()
            deflate()
        }
    }
    
    // Small payload - no compression
    val smallData = "Small payload"
    client.post("/small") {
        setBody(smallData)
    }
    
    // Large text payload - gzip compression
    val largeText = "Large text payload...".repeat(1000)
    client.post("/large-text") {
        compress("gzip")
        contentType(ContentType.Text.Plain)
        setBody(largeText)
    }
    
    // JSON data - gzip compression
    val jsonData = """{"users": [...], "data": [...]}"""
    client.post("/json-upload") {
        compress("gzip")
        contentType(ContentType.Application.Json)
        setBody(jsonData)
    }
    
    // Binary data - deflate compression
    val binaryData = ByteArray(10000) { it.toByte() }
    client.post("/binary-upload") {
        compress("deflate")
        contentType(ContentType.Application.OctetStream)
        setBody(binaryData)
    }
    
    // Multi-layer compression for maximum compression
    val criticalData = "Critical data requiring maximum compression"
    client.post("/critical") {
        compress("deflate", "gzip")  // Double compression
        setBody(criticalData)
    }
    
    client.close()
}

Install with Tessl CLI

npx tessl i tessl/maven-io-ktor--ktor-client-encoding

docs

configuration.md

encoders.md

index.md

request-compression.md

response-decompression.md

tile.json