Ktor HTTP client core library providing asynchronous HTTP client capabilities for Kotlin multiplatform applications
—
Advanced content processing including form data, multipart uploads, progress monitoring, content transformation, and support for various content types with streaming capabilities.
Different types of content that can be sent with HTTP requests.
/**
* Base class for outgoing HTTP content
*/
sealed class OutgoingContent {
/** Content length if known */
abstract val contentLength: Long?
/** Content type */
abstract val contentType: ContentType?
/** Content from byte array */
class ByteArrayContent(
val bytes: ByteArray
) : OutgoingContent() {
override val contentLength: Long get() = bytes.size.toLong()
override val contentType: ContentType? = null
}
/** Content from string with encoding */
class TextContent(
val text: String,
override val contentType: ContentType
) : OutgoingContent() {
override val contentLength: Long get() = text.toByteArray().size.toLong()
}
/** Content from ByteReadChannel for streaming */
class ReadChannelContent(
val readFrom: ByteReadChannel,
override val contentLength: Long? = null,
override val contentType: ContentType? = null
) : OutgoingContent()
/** Content using ByteWriteChannel */
class WriteChannelContent(
val body: suspend ByteWriteChannel.() -> Unit,
override val contentLength: Long? = null,
override val contentType: ContentType? = null
) : OutgoingContent()
/** Represents no content */
object NoContent : OutgoingContent() {
override val contentLength: Long = 0
override val contentType: ContentType? = null
}
}
/**
* Empty content object
*/
object EmptyContent : OutgoingContent() {
override val contentLength: Long = 0
override val contentType: ContentType? = null
}Usage Examples:
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.utils.io.*
val client = HttpClient()
// Byte array content
val imageBytes = File("image.jpg").readBytes()
client.post("https://api.example.com/upload") {
setBody(OutgoingContent.ByteArrayContent(imageBytes))
contentType(ContentType.Image.JPEG)
}
// Text content with encoding
client.post("https://api.example.com/text") {
setBody(OutgoingContent.TextContent(
"Hello, World!",
ContentType.Text.Plain.withCharset(Charsets.UTF_8)
))
}
// Streaming content from channel
val channel = ByteChannel()
launch {
channel.writeStringUtf8("Streaming data...")
channel.close()
}
client.post("https://api.example.com/stream") {
setBody(OutgoingContent.ReadChannelContent(channel))
}
// Write channel content
client.post("https://api.example.com/generate") {
setBody(OutgoingContent.WriteChannelContent({ writeChannel ->
writeChannel.writeStringUtf8("Generated content: ")
repeat(1000) { i ->
writeChannel.writeStringUtf8("$i ")
}
}))
}Handle URL-encoded and multipart form data.
/**
* URL-encoded form data content
*/
class FormDataContent(
val formData: Parameters
) : OutgoingContent()
/**
* Multipart form data content
*/
class MultiPartFormDataContent(
val parts: List<PartData>,
val boundary: String = generateBoundary(),
override val contentType: ContentType =
ContentType.MultiPart.FormData.withParameter("boundary", boundary)
) : OutgoingContent()
/**
* Form data builder DSL
*/
fun formData(block: FormBuilder.() -> Unit): List<PartData>
class FormBuilder {
/** Add text part */
fun append(key: String, value: String, headers: Headers = Headers.Empty)
/** Add file part */
fun append(
key: String,
filename: String,
contentType: ContentType? = null,
size: Long? = null,
headers: Headers = Headers.Empty,
block: suspend ByteWriteChannel.() -> Unit
)
/** Add bytes part */
fun append(
key: String,
value: ByteArray,
headers: Headers = Headers.Empty
)
}Usage Examples:
import io.ktor.client.request.forms.*
import io.ktor.http.*
// URL-encoded form
val formParameters = parametersOf(
"username" to listOf("john_doe"),
"email" to listOf("john@example.com"),
"age" to listOf("30")
)
client.post("https://api.example.com/register") {
setBody(FormDataContent(formParameters))
}
// Multipart form data
val multipartData = formData {
append("username", "john_doe")
append("email", "john@example.com")
append("profile_picture", "avatar.jpg", ContentType.Image.JPEG) {
writeFully(File("avatar.jpg").readBytes())
}
append("document", File("resume.pdf").readBytes())
}
client.post("https://api.example.com/profile") {
setBody(MultiPartFormDataContent(multipartData))
}
// Form submission helpers
client.submitForm(
url = "https://api.example.com/login",
formParameters = parametersOf(
"username" to listOf("john"),
"password" to listOf("secret")
)
)
client.submitFormWithBinaryData(
url = "https://api.example.com/upload",
formData = formData {
append("file", "document.pdf", ContentType.Application.Pdf) {
writeFileContent(File("document.pdf"))
}
append("description", "Important document")
}
)Monitor upload and download progress.
/**
* Progress listener for monitoring transfer progress
*/
typealias ProgressListener = (bytesSentTotal: Long, contentLength: Long) -> Unit
/**
* Monitor upload progress
*/
fun HttpRequestBuilder.onUpload(listener: ProgressListener)
/**
* Monitor download progress
*/
fun HttpRequestBuilder.onDownload(listener: ProgressListener)Usage Examples:
val largeFile = File("large-file.zip")
client.post("https://api.example.com/upload") {
setBody(largeFile.readBytes())
// Monitor upload progress
onUpload { bytesSentTotal, contentLength ->
val progress = (bytesSentTotal.toDouble() / contentLength * 100).toInt()
println("Upload progress: $progress% ($bytesSentTotal / $contentLength bytes)")
}
}
// Download with progress monitoring
client.get("https://api.example.com/download/large-file") {
onDownload { bytesReceivedTotal, contentLength ->
val progress = if (contentLength > 0) {
(bytesReceivedTotal.toDouble() / contentLength * 100).toInt()
} else {
0
}
println("Download progress: $progress% ($bytesReceivedTotal / $contentLength bytes)")
}
}Handle different types of multipart data.
/**
* Base class for multipart data parts
*/
sealed class PartData {
/** Part headers */
abstract val headers: Headers
/** Part name */
val name: String? get() = headers[HttpHeaders.ContentDisposition]
?.let { ContentDisposition.parse(it).name }
/** Form field part */
class FormItem(
val value: String,
override val headers: Headers
) : PartData()
/** File part */
class FileItem(
val provider: () -> ByteReadChannel,
override val headers: Headers
) : PartData() {
/** Original filename */
val originalFileName: String? get() = headers[HttpHeaders.ContentDisposition]
?.let { ContentDisposition.parse(it).parameter("filename") }
}
/** Binary data part */
class BinaryItem(
val provider: () -> ByteReadChannel,
override val headers: Headers
) : PartData()
/** Binary channel part */
class BinaryChannelItem(
val provider: () -> ByteReadChannel,
override val headers: Headers
) : PartData()
}
/**
* Content disposition header utilities
*/
class ContentDisposition private constructor(
val disposition: String,
val parameters: List<HeaderValueParam>
) {
companion object {
fun parse(value: String): ContentDisposition
val Inline = ContentDisposition("inline", emptyList())
val Attachment = ContentDisposition("attachment", emptyList())
val FormData = ContentDisposition("form-data", emptyList())
}
fun parameter(name: String): String?
fun withParameter(name: String, value: String): ContentDisposition
val name: String?
val filename: String?
}Transform and process content during transmission.
/**
* Content encoding for compression
*/
enum class ContentEncoding(val value: String) {
GZIP("gzip"),
DEFLATE("deflate"),
COMPRESS("compress"),
IDENTITY("identity")
}
/**
* Content transformation utilities
*/
object ContentTransformation {
/** Compress content with gzip */
suspend fun gzip(content: OutgoingContent): OutgoingContent
/** Compress content with deflate */
suspend fun deflate(content: OutgoingContent): OutgoingContent
/** Transform content with custom function */
suspend fun transform(
content: OutgoingContent,
transformer: suspend (ByteReadChannel) -> ByteReadChannel
): OutgoingContent
}Usage Examples:
// Manual content transformation
val originalContent = OutgoingContent.TextContent("Large text content...", ContentType.Text.Plain)
val compressedContent = ContentTransformation.gzip(originalContent)
client.post("https://api.example.com/data") {
setBody(compressedContent)
header(HttpHeaders.ContentEncoding, ContentEncoding.GZIP.value)
}
// Custom transformation
val transformedContent = ContentTransformation.transform(originalContent) { channel ->
// Custom transformation logic
val transformed = ByteChannel()
channel.copyTo(transformed) // Apply transformation
transformed
}Handle streaming content for large files and real-time data.
/**
* Channel writer content for streaming
*/
class ChannelWriterContent(
val body: suspend ByteWriteChannel.() -> Unit,
override val contentLength: Long? = null,
override val contentType: ContentType? = null
) : OutgoingContent()
/**
* Input stream content for reading from streams
*/
class InputStreamContent(
val inputStream: InputStream,
override val contentLength: Long? = null,
override val contentType: ContentType? = null
) : OutgoingContent()Usage Examples:
// Streaming upload
client.post("https://api.example.com/stream-upload") {
setBody(ChannelWriterContent { channel ->
// Stream data in chunks
repeat(1000) { chunk ->
val data = generateChunkData(chunk)
channel.writeFully(data)
channel.flush()
delay(10) // Simulate real-time streaming
}
})
}
// Upload from input stream
val fileInputStream = FileInputStream("large-file.dat")
client.post("https://api.example.com/upload-stream") {
setBody(InputStreamContent(
inputStream = fileInputStream,
contentLength = File("large-file.dat").length(),
contentType = ContentType.Application.OctetStream
))
}Utility functions for content handling.
/**
* Content utility functions
*/
object ContentUtils {
/** Get content length if available */
fun getContentLength(content: OutgoingContent): Long?
/** Check if content is empty */
fun isEmpty(content: OutgoingContent): Boolean
/** Convert content to byte array */
suspend fun toByteArray(content: OutgoingContent): ByteArray
/** Convert content to string */
suspend fun toString(content: OutgoingContent, charset: Charset = Charsets.UTF_8): String
}
/**
* Content type utilities
*/
fun ContentType.withCharset(charset: Charset): ContentType
fun ContentType.withParameter(name: String, value: String): ContentType
fun ContentType.match(other: ContentType): Boolean
fun ContentType.match(pattern: ContentType): Boolean// Content type definitions
class ContentType private constructor(
val contentType: String,
val contentSubtype: String,
val parameters: List<HeaderValueParam> = emptyList()
) {
fun withParameter(name: String, value: String): ContentType
fun withoutParameters(): ContentType
fun match(other: ContentType): Boolean
companion object {
fun parse(value: String): ContentType
object Any {
val Any = ContentType("*", "*")
}
object Application {
val Any = ContentType("application", "*")
val Atom = ContentType("application", "atom+xml")
val Json = ContentType("application", "json")
val JavaScript = ContentType("application", "javascript")
val OctetStream = ContentType("application", "octet-stream")
val FontWoff = ContentType("application", "font-woff")
val Rss = ContentType("application", "rss+xml")
val Xml = ContentType("application", "xml")
val Zip = ContentType("application", "zip")
val GZip = ContentType("application", "gzip")
val FormUrlEncoded = ContentType("application", "x-www-form-urlencoded")
val Pdf = ContentType("application", "pdf")
}
object Audio {
val Any = ContentType("audio", "*")
val MP4 = ContentType("audio", "mp4")
val MPEG = ContentType("audio", "mpeg")
val OGG = ContentType("audio", "ogg")
}
object Image {
val Any = ContentType("image", "*")
val GIF = ContentType("image", "gif")
val JPEG = ContentType("image", "jpeg")
val PNG = ContentType("image", "png")
val SVG = ContentType("image", "svg+xml")
val XIcon = ContentType("image", "x-icon")
}
object MultiPart {
val Any = ContentType("multipart", "*")
val Mixed = ContentType("multipart", "mixed")
val Alternative = ContentType("multipart", "alternative")
val Related = ContentType("multipart", "related")
val FormData = ContentType("multipart", "form-data")
val Signed = ContentType("multipart", "signed")
val Encrypted = ContentType("multipart", "encrypted")
val ByteRanges = ContentType("multipart", "byteranges")
}
object Text {
val Any = ContentType("text", "*")
val Plain = ContentType("text", "plain")
val CSS = ContentType("text", "css")
val CSV = ContentType("text", "csv")
val Html = ContentType("text", "html")
val JavaScript = ContentType("text", "javascript")
val VCard = ContentType("text", "vcard")
val Xml = ContentType("text", "xml")
val EventStream = ContentType("text", "event-stream")
}
object Video {
val Any = ContentType("video", "*")
val MPEG = ContentType("video", "mpeg")
val MP4 = ContentType("video", "mp4")
val OGG = ContentType("video", "ogg")
val QuickTime = ContentType("video", "quicktime")
}
}
}
// Parameter types
data class HeaderValueParam(
val name: String,
val value: String
)
// Parameters interface
interface Parameters {
operator fun get(name: String): String?
fun getAll(name: String): List<String>?
fun contains(name: String): Boolean
fun contains(name: String, value: String): Boolean
fun names(): Set<String>
fun isEmpty(): Boolean
fun entries(): Set<Map.Entry<String, List<String>>>
}
class ParametersBuilder {
fun append(name: String, value: String)
fun appendAll(name: String, values: Iterable<String>)
fun appendAll(parameters: Parameters)
fun appendMissing(parameters: Parameters)
fun set(name: String, value: String)
fun remove(name: String): Boolean
fun removeKeysWithNoEntries()
fun clear()
fun build(): Parameters
}Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-client-core-jvm