CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-ktor--ktor-server-compression-jvm

A Ktor server plugin that provides HTTP response compression and request decompression capabilities with support for gzip, deflate, and identity encoding algorithms.

Pending
Overview
Eval results
Files

control.mddocs/

Compression Control

The Compression plugin provides runtime control mechanisms to selectively suppress compression or decompression on a per-request basis, giving you fine-grained control over when these operations occur.

Suppression Functions

Suppress Response Compression

fun ApplicationCall.suppressCompression()

Prevents the current response from being compressed, regardless of plugin configuration:

routing {
    get("/no-compression") {
        call.suppressCompression()
        call.respondText("This response will never be compressed")
    }
    
    get("/conditional") {
        if (call.request.queryParameters["raw"] == "true") {
            call.suppressCompression()
        }
        call.respond("Conditionally compressed response")
    }
}

Suppress Request Decompression

fun ApplicationCall.suppressDecompression()

Prevents the current request body from being decompressed, allowing access to raw compressed data:

routing {
    post("/raw-upload") {
        call.suppressDecompression()
        
        // Receive compressed bytes directly
        val compressedData = call.receive<ByteArray>()
        
        // Process compressed data or decompress manually
        val originalSize = call.request.headers[HttpHeaders.ContentLength]?.toInt() ?: 0
        logger.info("Received $originalSize bytes of compressed data")
        
        call.respond("Processed raw compressed upload")
    }
}

Suppression Status Checking

Check Compression Suppression

val ApplicationCall.isCompressionSuppressed: Boolean

Determine if response compression has been suppressed for the current call:

routing {
    get("/debug-compression") {
        if (call.isCompressionSuppressed) {
            call.respond("Compression is suppressed")
        } else {
            call.respond("Compression is active")
        }
    }
}

Check Decompression Suppression

val ApplicationCall.isDecompressionSuppressed: Boolean

Determine if request decompression has been suppressed for the current call:

routing {
    post("/upload") {
        val message = if (call.isDecompressionSuppressed) {
            "Processing raw compressed data"
        } else {
            "Processing decompressed data"
        }
        
        logger.info(message)
        val data = call.receiveText()
        call.respond("Processed: ${data.length} characters")
    }
}

Use Cases

Pre-compressed Content

Serve pre-compressed static files without double compression:

routing {
    static("/assets") {
        files("static")
        // Ktor automatically calls suppressCompression() for pre-compressed files
        // like .gz, .br variants
    }
    
    get("/precompressed/{file}") {
        val filename = call.parameters["file"]
        val gzFile = File("compressed/$filename.gz")
        
        if (gzFile.exists()) {
            call.suppressCompression()
            call.response.header(HttpHeaders.ContentEncoding, "gzip")
            call.respondFile(gzFile)
        } else {
            call.respondText("File not found", status = HttpStatusCode.NotFound)
        }
    }
}

Binary Data Handling

Handle binary uploads that shouldn't be decompressed:

routing {
    post("/binary-upload") {
        val contentType = call.request.contentType()
        
        if (contentType?.match(ContentType.Application.OctetStream) == true) {
            // Keep binary data compressed for efficient storage
            call.suppressDecompression()
            
            val compressedBytes = call.receive<ByteArray>()
            // Store compressed data directly
            database.storeBinary(compressedBytes)
        } else {
            // Normal decompression for text/JSON data
            val content = call.receiveText()
            processTextContent(content)
        }
        
        call.respond("Upload processed")
    }
}

Performance Optimization

Skip compression for small responses or specific content types:

routing {
    get("/small-response") {
        val data = generateSmallResponse()
        
        if (data.length < 100) {
            // Skip compression overhead for very small responses
            call.suppressCompression()
        }
        
        call.respondText(data)
    }
    
    get("/image-proxy/{id}") {
        val imageData = imageService.getImage(call.parameters["id"])
        
        // Images are already compressed
        call.suppressCompression()
        call.respondBytes(imageData, ContentType.Image.JPEG)
    }
}

Debug and Development

Disable compression for easier debugging:

routing {
    get("/api/{...}") {
        if (call.request.headers["X-Debug-Mode"] == "true") {
            call.suppressCompression()
        }
        
        val response = apiService.processRequest(call)
        call.respond(response)
    }
}

Conditional Suppression with Interceptors

Apply suppression logic globally using interceptors:

install(createApplicationPlugin("CompressionControl") {
    onCall { call ->
        // Suppress compression for API documentation endpoints
        if (call.request.uri.startsWith("/docs/")) {
            call.suppressCompression()
        }
        
        // Suppress decompression for webhook endpoints that need raw data
        if (call.request.uri.startsWith("/webhooks/")) {
            call.suppressDecompression()
        }
    }
})

Implementation Notes

Suppression Attributes

The suppression system uses internal attribute keys:

internal val SuppressionAttribute: AttributeKey<Boolean>
internal val DecompressionSuppressionAttribute: AttributeKey<Boolean>

These attributes are automatically checked by the compression plugin during request/response processing.

Timing Requirements

Suppression calls must be made before the compression/decompression logic executes:

  • Compression suppression: Call before response content is processed
  • Decompression suppression: Call before request body is consumed
routing {
    post("/example") {
        // ✅ Correct: suppress before processing
        call.suppressDecompression()
        val body = call.receiveText()
        
        // ❌ Incorrect: too late to suppress compression
        call.suppressCompression()  // This won't work
        call.respond(body)
    }
}

Interaction with Conditions

Suppression overrides all configured conditions. Even if conditions would normally allow compression/decompression, suppression takes precedence:

install(Compression) {
    matchContentType(ContentType.Text.Plain)  // Condition allows text compression
}

routing {
    get("/text") {
        call.suppressCompression()  // Overrides condition - no compression occurs
        call.respondText("This text won't be compressed", ContentType.Text.Plain)
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-io-ktor--ktor-server-compression-jvm

docs

conditions.md

configuration.md

control.md

decompression.md

encoding.md

index.md

tile.json