Outgoing content abstractions for different content types including streaming, byte arrays, protocol upgrades, and comprehensive content handling utilities.
Base sealed class for all outgoing HTTP content types with common properties and extension support.
/**
* Base class for all outgoing HTTP content
*/
sealed class OutgoingContent {
/** Content type of the outgoing content */
abstract val contentType: ContentType?
/** Content length in bytes if known */
abstract val contentLength: Long?
/** HTTP status code to send with content */
abstract val status: HttpStatusCode?
/** Additional headers to send with content */
abstract val headers: Headers
/**
* Get extension property by key
* @param key attribute key
* @return property value or null
*/
fun <T : Any> getProperty(key: AttributeKey<T>): T?
/**
* Set extension property
* @param key attribute key
* @param value property value
*/
fun <T : Any> setProperty(key: AttributeKey<T>, value: T?)
/**
* Get trailing headers (HTTP/2 trailers)
* @return trailing headers or null
*/
open fun trailers(): Headers? = null
}Specific implementations for different content delivery patterns.
/**
* Content without payload (e.g., 204 No Content)
*/
object NoContent : OutgoingContent()
/**
* Content read from ByteReadChannel (streaming)
*/
abstract class ReadChannelContent : OutgoingContent() {
/**
* Get content channel for reading
* @return ByteReadChannel with content data
*/
abstract fun readFrom(): ByteReadChannel
/**
* Get content channel for specific byte range
* @param range byte range to read
* @return ByteReadChannel with range data
*/
open fun readFrom(range: LongRange): ByteReadChannel = readFrom()
}
/**
* Content written to ByteWriteChannel
*/
abstract class WriteChannelContent : OutgoingContent() {
/**
* Write content to output channel
* @param channel output channel to write to
*/
abstract suspend fun writeTo(channel: ByteWriteChannel)
}
/**
* Content as byte array (in-memory)
*/
abstract class ByteArrayContent : OutgoingContent() {
/**
* Get content as byte array
* @return content bytes
*/
abstract fun bytes(): ByteArray
}
/**
* Protocol upgrade content (e.g., WebSocket upgrade)
*/
abstract class ProtocolUpgrade : OutgoingContent() {
/** Always returns SwitchingProtocols status */
final override val status: HttpStatusCode get() = HttpStatusCode.SwitchingProtocols
/**
* Upgrade connection protocol
* @param input input channel
* @param output output channel
* @param engineContext engine coroutine context
* @param userContext user coroutine context
* @return Job managing the upgraded connection
*/
abstract suspend fun upgrade(
input: ByteReadChannel,
output: ByteWriteChannel,
engineContext: CoroutineContext,
userContext: CoroutineContext
): Job
}Special object representing absent content body.
/**
* Represents null/absent body content
*/
object NullBodyUsage Examples:
import io.ktor.http.content.*
import io.ktor.http.*
// Create content with no payload
val noContent = object : OutgoingContent.NoContent() {
override val contentType = null
override val contentLength = 0L
override val status = HttpStatusCode.NoContent
override val headers = headersOf()
}
// Create byte array content
val jsonContent = object : OutgoingContent.ByteArrayContent() {
private val data = """{"message": "Hello World"}""".toByteArray(Charsets.UTF_8)
override val contentType = ContentType.Application.Json
override val contentLength = data.size.toLong()
override val headers = headersOf()
override val status = HttpStatusCode.OK
override fun bytes(): ByteArray = data
}
// Create streaming content
val streamingContent = object : OutgoingContent.WriteChannelContent() {
override val contentType = ContentType.Text.Plain
override val contentLength = null // Unknown length
override val headers = headersOf()
override val status = HttpStatusCode.OK
override suspend fun writeTo(channel: ByteWriteChannel) {
repeat(100) { i ->
channel.writeStringUtf8("Line $i\n")
delay(10) // Simulate streaming delay
}
channel.close()
}
}
// Set extension properties
jsonContent.setProperty(AttributeKey("custom-info"), "Generated content")
val customInfo = jsonContent.getProperty(AttributeKey<String>("custom-info"))
// Check content properties
println("Content type: ${jsonContent.contentType}")
println("Content length: ${jsonContent.contentLength}")
println("Status: ${jsonContent.status}")
println("Has headers: ${!jsonContent.headers.isEmpty()}")
// Work with protocol upgrade (conceptual example)
val webSocketUpgrade = object : OutgoingContent.ProtocolUpgrade() {
override val contentType = null
override val contentLength = null
override val headers = headersOf(
HttpHeaders.Upgrade to "websocket",
HttpHeaders.Connection to "Upgrade"
)
override suspend fun upgrade(
input: ByteReadChannel,
output: ByteWriteChannel,
engineContext: CoroutineContext,
userContext: CoroutineContext
): Job {
// Implement WebSocket protocol handling
return Job()
}
}/**
* Attribute key for extension properties
*/
class AttributeKey<T : Any>(val name: String)
/**
* Coroutine context for async operations
* (Provided by Kotlin coroutines library)
*/
// CoroutineContext is from kotlin.coroutines
/**
* Job for managing coroutine lifecycle
* (Provided by Kotlin coroutines library)
*/
// Job is from kotlinx.coroutines
/**
* Byte channels for streaming I/O
* (Provided by Ktor I/O library)
*/
// ByteReadChannel and ByteWriteChannel are from io.ktor.utils.io