Ktor client plugin for content encoding support including compression and decompression
npx @tessl/cli install tessl/maven-io-ktor--ktor-client-encoding-jvm@3.2.0Ktor Client Content Encoding is a plugin that provides content encoding and compression capabilities for Ktor HTTP client applications. It enables automatic compression of request bodies and decompression of response bodies using standard algorithms like gzip, deflate, and identity encoding.
implementation("io.ktor:ktor-client-encoding-jvm:3.2.0")import io.ktor.client.plugins.compression.*import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.compression.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.client.call.*
// Create client with content encoding support
val client = HttpClient(CIO) {
install(ContentEncoding) {
gzip()
deflate()
identity()
}
}
// Client will automatically decompress responses and set Accept-Encoding header
val response: HttpResponse = client.get("https://api.example.com/data")
val responseText = response.bodyAsText()
// Check which decoders were applied to the response
val appliedDecoders = response.appliedDecoders
println("Applied decoders: $appliedDecoders")
// Compress request body
client.post("https://api.example.com/upload") {
compress("gzip")
setBody("large data payload")
}The ContentEncoding plugin is built around several key components:
ClientPlugin<ContentEncodingConfig>Install and configure the ContentEncoding plugin on an HttpClient.
val ContentEncoding: ClientPlugin<ContentEncodingConfig>
fun HttpClientConfig<*>.ContentEncoding(
mode: ContentEncodingConfig.Mode = ContentEncodingConfig.Mode.DecompressResponse,
block: ContentEncodingConfig.() -> Unit = {
gzip()
deflate()
identity()
}
)Configure encoding algorithms and compression modes.
@KtorDsl
class ContentEncodingConfig {
var mode: Mode
fun gzip(quality: Float? = null)
fun deflate(quality: Float? = null)
fun identity(quality: Float? = null)
fun customEncoder(encoder: ContentEncoder, quality: Float? = null)
}
enum class ContentEncodingConfig.Mode(
internal val request: Boolean,
internal val response: Boolean
) {
CompressRequest(true, false),
DecompressResponse(false, true),
All(true, true)
}Usage Examples:
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.compression.*
// Configure with specific mode and encoders
val client = HttpClient(CIO) {
ContentEncoding(mode = ContentEncodingConfig.Mode.All) {
gzip(quality = 1.0f)
deflate(quality = 0.8f)
// Custom encoder support
customEncoder(MyCustomEncoder, quality = 0.5f)
}
}
// Request compression only
val client = HttpClient(CIO) {
ContentEncoding(mode = ContentEncodingConfig.Mode.CompressRequest) {
gzip()
}
}Compress request bodies using specified encoding algorithms.
/**
* Compresses request body using ContentEncoding plugin
* @param contentEncoderName names of compression encoders to use
*/
fun HttpRequestBuilder.compress(vararg contentEncoderName: String)
/**
* Compress request body using ContentEncoding plugin
* @param contentEncoderNames names of compression encoders to use
*/
fun HttpRequestBuilder.compress(contentEncoderNames: List<String>)Usage Examples:
import io.ktor.client.request.*
// Single encoder
client.post("/upload") {
compress("gzip")
setBody(largeData)
}
// Multiple encoders applied in sequence
client.post("/upload") {
compress("deflate", "gzip") // Applied as deflate first, then gzip
setBody(largeData)
}
// Using list
client.post("/upload") {
compress(listOf("gzip", "deflate"))
setBody(largeData)
}Automatic decompression of response bodies and tracking of applied decoders.
/**
* List of ContentEncoder names that were used to decode response body
*/
val HttpResponse.appliedDecoders: List<String>Usage Examples:
import io.ktor.client.call.*
val response = client.get("https://compressed-api.example.com/data")
// Check what decompression was applied
val decoders = response.appliedDecoders
if (decoders.contains("gzip")) {
println("Response was gzip compressed")
}
// Response body is automatically decompressed
val content = response.bodyAsText()Built-in and extensible content encoder system.
interface ContentEncoder : Encoder {
val name: String
fun predictCompressedLength(contentLength: Long): Long?
}
interface Encoder {
fun encode(
source: ByteReadChannel,
coroutineContext: CoroutineContext = EmptyCoroutineContext
): ByteReadChannel
fun encode(
source: ByteWriteChannel,
coroutineContext: CoroutineContext = EmptyCoroutineContext
): ByteWriteChannel
fun decode(
source: ByteReadChannel,
coroutineContext: CoroutineContext = EmptyCoroutineContext
): ByteReadChannel
}
// Built-in encoders
object GZipEncoder : ContentEncoder {
override val name: String // = "gzip"
}
object DeflateEncoder : ContentEncoder {
override val name: String // = "deflate"
}
object IdentityEncoder : ContentEncoder {
override val name: String // = "identity"
override fun predictCompressedLength(contentLength: Long): Long
}Usage Examples:
import io.ktor.util.*
import io.ktor.utils.io.*
import kotlin.coroutines.*
// Using built-in encoders by name
compress(GZipEncoder.name)
compress("gzip") // Equivalent
// Custom encoder implementation
object BrotliEncoder : ContentEncoder {
override val name = "br"
override fun encode(source: ByteReadChannel, coroutineContext: CoroutineContext): ByteReadChannel {
// Custom implementation
}
override fun decode(source: ByteReadChannel, coroutineContext: CoroutineContext): ByteReadChannel {
// Custom implementation
}
}
// Register custom encoder
val client = HttpClient(CIO) {
install(ContentEncoding) {
customEncoder(BrotliEncoder, quality = 0.9f)
}
}Exception handling for unsupported encoding algorithms.
class UnsupportedContentEncodingException(encoding: String) :
IllegalStateException("Content-Encoding: $encoding unsupported.")Usage Examples:
try {
client.post("/upload") {
compress("unknown-algorithm")
setBody(data)
}
} catch (e: UnsupportedContentEncodingException) {
println("Encoding not supported: ${e.message}")
}import io.ktor.util.*
import io.ktor.client.plugins.api.*
import io.ktor.http.content.*
// Request/Response pipeline attributes (internal use)
internal val CompressionListAttribute: AttributeKey<List<String>>
internal val DecompressionListAttribute: AttributeKey<List<String>>
// Hook interfaces for internal pipeline integration
internal object AfterRenderHook : ClientHook<suspend (HttpRequestBuilder, OutgoingContent) -> OutgoingContent?>
internal object ReceiveStateHook : ClientHook<suspend (HttpResponse) -> HttpResponse?>