JVM-specific HTTP utilities and extensions for the Ktor framework providing URL utilities, file content type detection, HTTP message properties, and content handling for JVM platforms
—
Outgoing content system with JVM-specific implementations for streams, writers, and file content. Provides base classes and concrete implementations for handling various types of HTTP response content.
Abstract base class for all outgoing HTTP content with property management and caching support.
/**
* Base class for outgoing HTTP content
*/
abstract class OutgoingContent {
/**
* Content type of the response
*/
open val contentType: ContentType?
/**
* Content length in bytes if known
*/
open val contentLength: Long?
/**
* HTTP status code for the response
*/
open val status: HttpStatusCode?
/**
* Additional HTTP headers
*/
open val headers: Headers?
/**
* Trailing headers (HTTP/1.1 chunked encoding)
*/
open fun trailers(): Headers?
/**
* Get property by key
*/
fun <T : Any> getProperty(key: AttributeKey<T>): T?
/**
* Set property value
*/
fun <T : Any> setProperty(key: AttributeKey<T>, value: T?)
}
/**
* Check if content is empty
*/
fun OutgoingContent.isEmpty(): BooleanContent backed by a byte array for small, in-memory content.
/**
* Outgoing content backed by byte array
*/
abstract class OutgoingContent.ByteArrayContent : OutgoingContent() {
/**
* Get content as byte array
*/
abstract fun bytes(): ByteArray
}
/**
* Concrete byte array content implementation
*/
class ByteArrayContent(
private val bytes: ByteArray,
override val contentType: ContentType,
override val status: HttpStatusCode? = null
) : OutgoingContent.ByteArrayContent() {
override val contentLength: Long = bytes.size.toLong()
override fun bytes(): ByteArray = bytes
}Text content with automatic charset handling and encoding.
/**
* Text content with charset support
*/
class TextContent(
private val text: String,
override val contentType: ContentType,
override val status: HttpStatusCode? = null
) : OutgoingContent.ByteArrayContent() {
override val contentLength: Long
/**
* Get original text
*/
val text: String
override fun bytes(): ByteArray
}Content that writes data to a ByteWriteChannel for streaming.
/**
* Content that writes to a channel
*/
abstract class OutgoingContent.WriteChannelContent : OutgoingContent() {
/**
* Write content to the channel
* @param channel channel to write to
*/
abstract suspend fun writeTo(channel: ByteWriteChannel)
}
/**
* Channel writer content with suspend function body
*/
class ChannelWriterContent(
private val body: suspend ByteWriteChannel.() -> Unit,
override val contentType: ContentType,
override val status: HttpStatusCode? = null,
override val contentLength: Long? = null
) : OutgoingContent.WriteChannelContent() {
override suspend fun writeTo(channel: ByteWriteChannel) {
channel.body()
}
}Content that provides data through a ByteReadChannel for streaming reads.
/**
* Content that provides a read channel
*/
abstract class OutgoingContent.ReadChannelContent : OutgoingContent() {
/**
* Create read channel for full content
*/
abstract fun readFrom(): ByteReadChannel
/**
* Create read channel for content range
* @param range byte range to read
*/
open fun readFrom(range: LongRange): ByteReadChannel
}JVM-specific content implementations using Java I/O streams and writers.
/**
* Content that writes to an OutputStream (JVM-specific)
*/
class OutputStreamContent(
private val body: suspend OutputStream.() -> Unit,
override val contentType: ContentType,
override val status: HttpStatusCode? = null,
override val contentLength: Long? = null
) : OutgoingContent.WriteChannelContent() {
override suspend fun writeTo(channel: ByteWriteChannel)
}/**
* Content that writes to a Writer with charset handling (JVM-specific)
*/
class WriterContent(
private val body: suspend Writer.() -> Unit,
override val contentType: ContentType,
override val status: HttpStatusCode? = null,
override val contentLength: Long? = null
) : OutgoingContent.WriteChannelContent() {
override suspend fun writeTo(channel: ByteWriteChannel)
}/**
* Content served from URI/URL (JVM-specific)
*/
class URIFileContent(
val uri: URI,
override val contentType: ContentType = ContentType.defaultForFilePath(uri.path),
override val contentLength: Long? = null
) : OutgoingContent.ReadChannelContent() {
/**
* Constructor from URL
*/
constructor(
url: URL,
contentType: ContentType = ContentType.defaultForFilePath(url.path)
)
override fun readFrom(): ByteReadChannel
}Content for responses without body (like HTTP 204).
/**
* Content representing no content
*/
abstract class OutgoingContent.NoContent : OutgoingContent()Content for protocol upgrade responses (like WebSocket).
/**
* Content for protocol upgrade
*/
abstract class OutgoingContent.ProtocolUpgrade : OutgoingContent() {
override val status: HttpStatusCode = HttpStatusCode.SwitchingProtocols
/**
* Perform protocol upgrade
*/
abstract suspend fun upgrade(
input: ByteReadChannel,
output: ByteWriteChannel,
engineContext: CoroutineContext,
userContext: CoroutineContext
)
}Base class for content wrappers that delegate to another content.
/**
* Content wrapper that delegates to another content
*/
abstract class OutgoingContent.ContentWrapper(
private val delegate: OutgoingContent
) : OutgoingContent() {
/**
* Create copy with different delegate
*/
abstract fun copy(delegate: OutgoingContent): ContentWrapper
/**
* Get wrapped content
*/
fun delegate(): OutgoingContent
override val contentType: ContentType? get() = delegate.contentType
override val contentLength: Long? get() = delegate.contentLength
override val status: HttpStatusCode? get() = delegate.status
override val headers: Headers? get() = delegate.headers
}Utilities for compressing outgoing content.
/**
* Compress content using specified encoder
* @param encoder content encoder to use
* @param coroutineContext context for compression
* @return compressed content
*/
fun OutgoingContent.compressed(
encoder: ContentEncoder,
coroutineContext: CoroutineContext = EmptyCoroutineContext
): OutgoingContentCaching options and utilities for outgoing content.
/**
* Caching options for content
*/
data class CachingOptions(
val cacheControl: CacheControl? = null,
val expires: GMTDate? = null
)
/**
* Get caching options from content
*/
val OutgoingContent.caching: CachingOptions?
/**
* Set caching options for content
*/
fun OutgoingContent.setCaching(options: CachingOptions)
/**
* Property key for caching options
*/
val cachingProperty: AttributeKey<CachingOptions>Special content types for specific use cases.
/**
* Represents null/empty body
*/
object NullBodyUsage Examples:
import io.ktor.http.*
import io.ktor.http.content.*
import java.io.*
import java.net.URI
// Simple text content
val textResponse = TextContent(
text = "Hello, World!",
contentType = ContentType.Text.Plain.withCharset(Charsets.UTF_8),
status = HttpStatusCode.OK
)
// Byte array content
val jsonBytes = """{"message": "Hello"}""".toByteArray()
val jsonResponse = ByteArrayContent(
bytes = jsonBytes,
contentType = ContentType.Application.Json,
status = HttpStatusCode.OK
)
// JVM-specific: OutputStream content
val streamContent = OutputStreamContent(
body = { outputStream ->
outputStream.write("Hello from OutputStream".toByteArray())
outputStream.flush()
},
contentType = ContentType.Text.Plain,
status = HttpStatusCode.OK
)
// JVM-specific: Writer content with charset handling
val writerContent = WriterContent(
body = { writer ->
writer.write("Hello from Writer")
writer.flush()
},
contentType = ContentType.Text.Html.withCharset(Charsets.UTF_8),
status = HttpStatusCode.OK
)
// JVM-specific: File content from URI
val fileUri = URI("file:///path/to/document.pdf")
val fileContent = URIFileContent(
uri = fileUri,
contentType = ContentType.Application.Pdf
)
// Channel writer for streaming
val streamingContent = ChannelWriterContent(
body = { channel ->
channel.writeStringUtf8("Chunk 1\n")
channel.flush()
delay(100)
channel.writeStringUtf8("Chunk 2\n")
channel.flush()
},
contentType = ContentType.Text.Plain,
contentLength = null // Unknown length for streaming
)
// Content with caching
val cachedContent = TextContent("Cached response", ContentType.Text.Plain)
cachedContent.setCaching(CachingOptions(
cacheControl = CacheControl.MaxAge(maxAgeSeconds = 3600),
expires = GMTDate() + 3600_000 // 1 hour from now
))
// Compressed content
val compressedContent = textResponse.compressed(
encoder = GzipEncoder,
coroutineContext = Dispatchers.IO
)
// Check content properties
println("Content type: ${textResponse.contentType}")
println("Content length: ${textResponse.contentLength}")
println("Status: ${textResponse.status}")
println("Is empty: ${textResponse.isEmpty()}")
// Access content data
when (val content = response.content) {
is TextContent -> println("Text: ${content.text}")
is ByteArrayContent -> println("Bytes: ${content.bytes().size}")
is URIFileContent -> println("URI: ${content.uri}")
}All types are defined above in their respective capability sections.
/**
* Property key for version list
*/
val versionListProperty: AttributeKey<List<Version>>
/**
* Get versions from content
*/
val OutgoingContent.versions: List<Version>
/**
* Set versions for content
*/
fun OutgoingContent.setVersions(versions: List<Version>)Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-http-jvm