Ktor HTTP client core library providing asynchronous HTTP client functionality for multiplatform applications with macOS ARM64 support.
—
Content handling for form data, multipart uploads, file uploads, and various content types with proper encoding and streaming support for HTTP client requests.
Standard HTML form submission with URL-encoded and multipart formats.
/**
* Submit HTML form with URL-encoded parameters
* @param url Target URL for form submission
* @param formParameters Form parameters to submit
* @param encodeInQuery Whether to encode parameters in query string (GET-style)
* @param block Additional request configuration
* @return HTTP response
*/
suspend fun HttpClient.submitForm(
url: String,
formParameters: Parameters = Parameters.Empty,
encodeInQuery: Boolean = false,
block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse
/**
* Submit form with binary data using multipart/form-data
* @param url Target URL for form submission
* @param formData List of form parts including files
* @param block Additional request configuration
* @return HTTP response
*/
suspend fun HttpClient.submitFormWithBinaryData(
url: String,
formData: List<PartData>,
block: HttpRequestBuilder.() -> Unit = {}
): HttpResponseUsage Examples:
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
val client = HttpClient()
// Simple form submission
val formResponse = client.submitForm(
url = "https://example.com/submit",
formParameters = Parameters.build {
append("username", "john_doe")
append("password", "secret123")
append("remember", "true")
}
)
// Form submission with query encoding (GET-style)
val getFormResponse = client.submitForm(
url = "https://example.com/search",
formParameters = Parameters.build {
append("q", "kotlin http client")
append("page", "1")
append("limit", "10")
},
encodeInQuery = true
)
// Multipart form with file upload
val uploadResponse = client.submitFormWithBinaryData(
url = "https://example.com/upload",
formData = formData {
append("description", "File upload example")
append("category", "documents")
append("file", File("document.pdf").readBytes(), Headers.build {
append(HttpHeaders.ContentType, "application/pdf")
append(HttpHeaders.ContentDisposition, "filename=\"document.pdf\"")
})
}
)URL-encoded form data content for standard form submissions.
/**
* Content class for URL-encoded form data (application/x-www-form-urlencoded)
*/
class FormDataContent(
private val formData: Parameters
) : OutgoingContent.ByteArrayContent() {
override val contentType: ContentType = ContentType.Application.FormUrlEncoded
override val contentLength: Long?
override fun bytes(): ByteArray
}Usage Examples:
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
val client = HttpClient()
// Using FormDataContent directly
val formData = Parameters.build {
append("email", "user@example.com")
append("name", "John Smith")
append("age", "30")
append("interests", "kotlin")
append("interests", "programming")
}
val response = client.post("https://api.example.com/users") {
setBody(FormDataContent(formData))
}
// Equivalent to submitForm but with more control
val controlledResponse = client.post("https://api.example.com/login") {
headers {
append("X-Client-Version", "1.0")
}
setBody(FormDataContent(Parameters.build {
append("username", "user")
append("password", "pass")
append("device_id", "mobile-123")
}))
}Multipart form data for file uploads and mixed content types.
/**
* Content class for multipart/form-data submissions
*/
class MultiPartFormDataContent(
private val formData: List<PartData>,
override val contentType: ContentType = ContentType.MultiPart.FormData.withParameter("boundary", boundary),
override val contentLength: Long? = null
) : OutgoingContent.WriteChannelContent() {
override suspend fun writeTo(channel: ByteWriteChannel)
}
/**
* Multipart form data builder
*/
class MultiPartFormDataContent.Builder {
/** Add text part */
fun append(key: String, value: String)
/** Add text part with headers */
fun append(key: String, value: String, headers: Headers)
/** Add binary part */
fun append(key: String, data: ByteArray, headers: Headers = Headers.Empty)
/** Add input provider part */
fun append(key: String, provider: InputProvider, headers: Headers = Headers.Empty)
/** Add channel provider part */
fun append(key: String, channelProvider: ChannelProvider, headers: Headers = Headers.Empty)
/** Build the multipart content */
fun build(): MultiPartFormDataContent
}
/**
* DSL for building multipart form data
*/
fun formData(block: MultiPartFormDataContent.Builder.() -> Unit): List<PartData>Usage Examples:
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
import java.io.File
val client = HttpClient()
// File upload with additional form fields
val uploadResponse = client.post("https://api.example.com/upload") {
setBody(MultiPartFormDataContent(
formData {
// Text fields
append("title", "My Document")
append("description", "Important document for project")
append("category", "reports")
// File upload
append("document", File("report.pdf").readBytes(), Headers.build {
append(HttpHeaders.ContentType, "application/pdf")
append(HttpHeaders.ContentDisposition, "filename=\"report.pdf\"")
})
// Image upload
append("thumbnail", File("thumb.jpg").readBytes(), Headers.build {
append(HttpHeaders.ContentType, "image/jpeg")
append(HttpHeaders.ContentDisposition, "filename=\"thumbnail.jpg\"")
})
}
))
}
// Multiple file upload
val multiFileResponse = client.post("https://api.example.com/batch-upload") {
setBody(MultiPartFormDataContent(
formData {
append("batch_id", "batch-001")
append("user_id", "12345")
// Multiple files
listOf("file1.txt", "file2.txt", "file3.txt").forEach { filename ->
append("files", File(filename).readBytes(), Headers.build {
append(HttpHeaders.ContentType, ContentType.Text.Plain.toString())
append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
})
}
}
))
}
// Mixed content types
val mixedResponse = client.post("https://api.example.com/mixed") {
setBody(MultiPartFormDataContent(
formData {
// JSON data
append("metadata", """{"version": "1.0", "author": "John"}""", Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Json.toString())
})
// XML data
append("config", "<config><enabled>true</enabled></config>", Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Xml.toString())
})
// Binary data
append("data", byteArrayOf(0x89, 0x50, 0x4E, 0x47), Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.OctetStream.toString())
})
}
))
}Individual part representations for multipart content.
/**
* Represents a single part in multipart content
*/
sealed class PartData {
/** Part headers */
abstract val headers: Headers
/** Dispose part resources */
abstract fun dispose()
}
/**
* Text/string part data
*/
class PartData.FormItem(
val value: String,
override val headers: Headers
) : PartData()
/**
* Binary part data
*/
class PartData.BinaryItem(
val provider: () -> Input,
override val headers: Headers,
val contentLength: Long? = null
) : PartData()
/**
* File part data
*/
class PartData.FileItem(
val provider: () -> Input,
override val headers: Headers,
val contentLength: Long? = null
) : PartData()
/**
* Channel-based part data for streaming
*/
class PartData.BinaryChannelItem(
val provider: () -> ByteReadChannel,
override val headers: Headers,
val contentLength: Long? = null
) : PartData()Usage Examples:
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
import io.ktor.utils.io.*
val client = HttpClient()
// Custom part data creation
val customParts = listOf(
// Text part
PartData.FormItem(
value = "Custom form value",
headers = Headers.build {
append(HttpHeaders.ContentDisposition, "form-data; name=\"custom_field\"")
}
),
// Binary part
PartData.BinaryItem(
provider = { File("data.bin").inputStream().asInput() },
headers = Headers.build {
append(HttpHeaders.ContentDisposition, "form-data; name=\"binary_data\"; filename=\"data.bin\"")
append(HttpHeaders.ContentType, ContentType.Application.OctetStream.toString())
},
contentLength = File("data.bin").length()
),
// Streaming part
PartData.BinaryChannelItem(
provider = {
// Create a channel with streaming data
produce {
repeat(1000) {
channel.writeStringUtf8("Line $it\n")
}
}
},
headers = Headers.build {
append(HttpHeaders.ContentDisposition, "form-data; name=\"stream_data\"; filename=\"stream.txt\"")
append(HttpHeaders.ContentType, ContentType.Text.Plain.toString())
}
)
)
val customResponse = client.post("https://api.example.com/custom-upload") {
setBody(MultiPartFormDataContent(customParts))
}Builder for creating URL parameters and form data.
/**
* Builder for creating Parameters collections
*/
class ParametersBuilder(size: Int = 8) : StringValuesBuilder {
/** Append single parameter value */
fun append(name: String, value: String)
/** Append all values from StringValues */
fun appendAll(stringValues: StringValues)
/** Append all values for specific name */
fun appendAll(name: String, values: Iterable<String>)
/** Append missing values from StringValues */
fun appendMissing(stringValues: StringValues)
/** Append missing values for specific name */
fun appendMissing(name: String, values: Iterable<String>)
/** Set single parameter value (replace existing) */
fun set(name: String, value: String)
/** Set all values from StringValues */
fun setAll(stringValues: StringValues)
/** Set all values for specific name */
fun setAll(name: String, values: Iterable<String>)
/** Remove parameter by name */
fun remove(name: String): Boolean
/** Remove parameters with no values */
fun removeKeysWithNoEntries()
/** Clear all parameters */
fun clear()
/** Build final Parameters instance */
fun build(): Parameters
}
/**
* Parameters collection interface
*/
interface Parameters : StringValues {
companion object {
/** Empty parameters instance */
val Empty: Parameters
/** Build parameters using DSL */
inline fun build(builder: ParametersBuilder.() -> Unit): Parameters
}
}Usage Examples:
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
// Building parameters
val params = Parameters.build {
append("query", "kotlin")
append("sort", "relevance")
append("filter", "language:kotlin")
append("filter", "type:repository")
// Conditional parameters
if (includeArchived) {
append("archived", "true")
}
// From existing parameters
appendAll(existingParams)
// Set (replace) parameter
set("per_page", "50")
}
// Complex parameter building
val searchParams = ParametersBuilder().apply {
append("q", searchQuery)
append("page", currentPage.toString())
append("size", pageSize.toString())
// Multiple values
selectedCategories.forEach { category ->
append("category", category)
}
// Conditional parameters
if (sortBy.isNotEmpty()) {
append("sort", sortBy)
append("order", sortOrder)
}
// Date range
startDate?.let { append("start_date", it.toString()) }
endDate?.let { append("end_date", it.toString()) }
}.build()
// Use in request
val searchResponse = client.get("https://api.example.com/search") {
url {
parameters.appendAll(searchParams)
}
}
// Use in form submission
val formResponse = client.submitForm(
url = "https://example.com/search",
formParameters = searchParams
)Content type utilities for form and multipart handling.
/**
* Common content types for form data
*/
object ContentType {
object Application {
val FormUrlEncoded = ContentType("application", "x-www-form-urlencoded")
val Json = ContentType("application", "json")
val Xml = ContentType("application", "xml")
val OctetStream = ContentType("application", "octet-stream")
val Pdf = ContentType("application", "pdf")
}
object Text {
val Plain = ContentType("text", "plain")
val Html = ContentType("text", "html")
val Css = ContentType("text", "css")
val JavaScript = ContentType("text", "javascript")
}
object Image {
val Jpeg = ContentType("image", "jpeg")
val Png = ContentType("image", "png")
val Gif = ContentType("image", "gif")
val WebP = ContentType("image", "webp")
}
object MultiPart {
val FormData = ContentType("multipart", "form-data")
val Mixed = ContentType("multipart", "mixed")
val Alternative = ContentType("multipart", "alternative")
}
}
/**
* Content disposition utilities
*/
object ContentDisposition {
fun attachment(filename: String? = null): String
fun inline(filename: String? = null): String
fun formData(name: String, filename: String? = null): String
}Usage Examples:
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
// File upload with proper content types
val fileUploadResponse = client.post("https://api.example.com/files") {
setBody(MultiPartFormDataContent(
formData {
// Document
append("document", pdfBytes, Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Pdf.toString())
append(HttpHeaders.ContentDisposition, ContentDisposition.formData("document", "report.pdf"))
})
// Image
append("image", imageBytes, Headers.build {
append(HttpHeaders.ContentType, ContentType.Image.Jpeg.toString())
append(HttpHeaders.ContentDisposition, ContentDisposition.formData("image", "photo.jpg"))
})
// JSON metadata
append("metadata", jsonString, Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Json.toString())
append(HttpHeaders.ContentDisposition, ContentDisposition.formData("metadata"))
})
}
))
}interface StringValues {
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>>>
fun toMap(): Map<String, List<String>>
}
interface StringValuesBuilder {
fun get(name: String): String?
fun getAll(name: String): List<String>?
fun contains(name: String): Boolean
fun names(): Set<String>
fun isEmpty(): Boolean
fun entries(): Set<Map.Entry<String, List<String>>>
fun append(name: String, value: String)
fun appendAll(stringValues: StringValues)
fun appendAll(name: String, values: Iterable<String>)
fun appendMissing(stringValues: StringValues)
fun appendMissing(name: String, values: Iterable<String>)
fun set(name: String, value: String)
fun setAll(stringValues: StringValues)
fun setAll(name: String, values: Iterable<String>)
fun remove(name: String): Boolean
fun removeKeysWithNoEntries()
fun clear()
fun build(): StringValues
}
typealias InputProvider = () -> Input
typealias ChannelProvider = () -> ByteReadChannel
class Input : Closeable {
fun readAvailable(buffer: ByteArray): Int
fun readFully(buffer: ByteArray)
override fun close()
}Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-client-core-macosarm64