Ktor HTTP Client Core - a multiplatform asynchronous HTTP client library for Kotlin providing comprehensive HTTP request/response handling with plugin architecture.
—
Comprehensive form submission and file upload functionality including URL-encoded forms, multipart forms, and binary data handling.
Submit forms with URL-encoded and multipart data formats.
/**
* Submit URL-encoded form data
* @param url Target URL
* @param formParameters Form fields as Parameters
* @param encodeInQuery Whether to encode parameters in query string (GET)
* @returns HttpResponse
*/
suspend fun HttpClient.submitForm(
url: String,
formParameters: Parameters = Parameters.Empty,
encodeInQuery: Boolean = false
): HttpResponse
/**
* Submit multipart form data with binary support
* @param url Target URL
* @param formData List of form parts including files
* @returns HttpResponse
*/
suspend fun HttpClient.submitFormWithBinaryData(
url: String,
formData: List<PartData>
): HttpResponseUsage Examples:
val client = HttpClient()
// Submit simple form
val response = client.submitForm(
url = "https://example.com/login",
formParameters = Parameters.build {
append("username", "john")
append("password", "secret")
append("remember", "true")
}
)
// Submit form as GET request with query parameters
val searchResponse = client.submitForm(
url = "https://example.com/search",
formParameters = Parameters.build {
append("q", "kotlin")
append("type", "code")
},
encodeInQuery = true
)
// Submit multipart form with file
val uploadResponse = client.submitFormWithBinaryData(
url = "https://example.com/upload",
formData = formData {
append("description", "Profile photo")
append("category", "image")
append("file", File("photo.jpg").readBytes(), Headers.build {
append(HttpHeaders.ContentType, "image/jpeg")
append(HttpHeaders.ContentDisposition, """form-data; name="file"; filename="photo.jpg"""")
})
}
)Content classes for different form data types.
/**
* URL-encoded form data content
*/
class FormDataContent(
val formData: Parameters
) : OutgoingContent.ByteArrayContent() {
override val contentType: ContentType
override val contentLength: Long?
override fun bytes(): ByteArray
}
/**
* Multipart form data content
*/
class MultiPartFormDataContent(
val parts: List<PartData>,
val boundary: String = generateBoundary()
) : OutgoingContent.WriteChannelContent() {
override val contentType: ContentType
override suspend fun writeTo(channel: ByteWriteChannel)
}Usage Examples:
// Create form data content manually
val formContent = FormDataContent(Parameters.build {
append("name", "John Doe")
append("email", "john@example.com")
})
val response = client.post("https://example.com/users") {
setBody(formContent)
}
// Create multipart content manually
val multipartContent = MultiPartFormDataContent(
parts = listOf(
PartData.FormItem("John Doe", dispose = {}, partHeaders = Headers.build {
append(HttpHeaders.ContentDisposition, """form-data; name="name"""")
}),
PartData.FileItem(
provider = { File("document.pdf").readChannel() },
dispose = {},
partHeaders = Headers.build {
append(HttpHeaders.ContentType, "application/pdf")
append(HttpHeaders.ContentDisposition, """form-data; name="file"; filename="document.pdf"""")
}
)
)
)
val uploadResponse = client.post("https://example.com/upload") {
setBody(multipartContent)
}DSL for building form data with type safety and convenience methods.
/**
* Build form data using DSL
* @param block Form building block
* @returns List of PartData for multipart submission
*/
fun formData(block: FormBuilder.() -> Unit): List<PartData>
/**
* Form builder DSL class
*/
class FormBuilder {
/**
* Append text field
* @param key Field name
* @param value Field value
* @param headers Optional headers for this part
*/
fun append(key: String, value: String, headers: Headers = Headers.Empty)
/**
* Append binary data
* @param key Field name
* @param value Binary data
* @param headers Headers including content type and disposition
*/
fun append(key: String, value: ByteArray, headers: Headers = Headers.Empty)
/**
* Append file input stream
* @param key Field name
* @param value Input provider function
* @param headers Headers including content type and disposition
*/
fun append(key: String, value: () -> ByteReadChannel, headers: Headers = Headers.Empty)
/**
* Append channel content
* @param key Field name
* @param value Channel with content
* @param headers Headers including content type and disposition
*/
fun appendInput(key: String, value: ByteReadChannel, headers: Headers = Headers.Empty)
/**
* Append Input object (unified input abstraction)
* @param key Field name
* @param value Input object
* @param headers Headers including content type and disposition
*/
fun appendInput(key: String, value: Input, headers: Headers = Headers.Empty)
}Usage Examples:
// Build complex form data
val formData = formData {
// Text fields
append("name", "John Doe")
append("email", "john@example.com")
append("age", "30")
// File from bytes
val imageBytes = File("profile.jpg").readBytes()
append("profile_image", imageBytes, Headers.build {
append(HttpHeaders.ContentType, "image/jpeg")
append(HttpHeaders.ContentDisposition, """form-data; name="profile_image"; filename="profile.jpg"""")
})
// File from stream provider
append("document", { File("report.pdf").readChannel() }, Headers.build {
append(HttpHeaders.ContentType, "application/pdf")
append(HttpHeaders.ContentDisposition, """form-data; name="document"; filename="report.pdf"""")
})
// Input stream
val dataStream = ByteReadChannel("large data content")
appendInput("data_file", dataStream, Headers.build {
append(HttpHeaders.ContentType, "text/plain")
append(HttpHeaders.ContentDisposition, """form-data; name="data_file"; filename="data.txt"""")
})
}
val response = client.submitFormWithBinaryData("https://example.com/upload", formData)Sealed class hierarchy for different types of form parts.
/**
* Base class for form parts
*/
sealed class PartData {
/** Headers for this part */
abstract val headers: Headers
/** Cleanup function */
abstract val dispose: () -> Unit
/**
* Text form field
*/
data class FormItem(
val value: String,
override val dispose: () -> Unit,
override val headers: Headers = Headers.Empty
) : PartData()
/**
* File form field with streaming content
*/
data class FileItem(
val provider: () -> ByteReadChannel,
override val dispose: () -> Unit,
override val headers: Headers = Headers.Empty
) : PartData()
/**
* Binary data form field
*/
data class BinaryItem(
val provider: () -> ByteArray,
override val dispose: () -> Unit,
override val headers: Headers = Headers.Empty
) : PartData()
/**
* Channel-based form field
*/
data class BinaryChannelItem(
val provider: () -> ByteReadChannel,
override val dispose: () -> Unit,
override val headers: Headers = Headers.Empty
) : PartData()
}Builder for URL-encoded form parameters.
/**
* Parameters collection interface
*/
interface Parameters : StringValues {
companion object {
val Empty: Parameters
/**
* Build parameters using DSL
* @param block Parameter building block
* @returns Parameters collection
*/
fun build(block: ParametersBuilder.() -> Unit): Parameters
}
}
/**
* Parameters builder class
*/
class ParametersBuilder : StringValuesBuilder() {
/**
* Append parameter value
* @param name Parameter name
* @param value Parameter value
*/
override fun append(name: String, value: String)
/**
* Append all parameters from another collection
* @param parameters Source parameters
*/
fun appendAll(parameters: Parameters)
/**
* Append parameter with multiple values
* @param name Parameter name
* @param values Parameter values
*/
fun appendAll(name: String, values: Iterable<String>)
/**
* Set parameter value (replaces existing)
* @param name Parameter name
* @param value Parameter value
*/
override fun set(name: String, value: String)
/**
* Build final Parameters collection
* @returns Immutable Parameters
*/
override fun build(): Parameters
}Usage Examples:
// Build parameters for form submission
val params = Parameters.build {
append("username", "john")
append("email", "john@example.com")
append("interests", "kotlin")
append("interests", "programming") // Multiple values for same key
// Conditional parameters
if (rememberMe) {
append("remember", "true")
}
// Append from map
val additionalParams = mapOf("source" to "mobile", "version" to "1.0")
additionalParams.forEach { (key, value) ->
append(key, value)
}
}
val response = client.submitForm("https://example.com/register", params)
// Access parameter values
val username = params["username"] // Single value
val interests = params.getAll("interests") // All values for key
val allNames = params.names() // All parameter namesUtilities for setting proper content types and headers for form data.
/**
* Generate multipart boundary string
* @returns Random boundary string
*/
fun generateBoundary(): String
/**
* Create Content-Disposition header for form field
* @param name Field name
* @param filename Optional filename
* @returns Content-Disposition header value
*/
fun formDataContentDisposition(name: String, filename: String? = null): String
/**
* Common content types for file uploads
*/
object ContentTypes {
val MultipartFormData: ContentType
val ApplicationFormUrlEncoded: ContentType
val ApplicationOctetStream: ContentType
val TextPlain: ContentType
val ImageJpeg: ContentType
val ImagePng: ContentType
val ApplicationPdf: ContentType
}Usage Examples:
// Custom headers for file upload
val headers = Headers.build {
append(HttpHeaders.ContentType, "image/png")
append(HttpHeaders.ContentDisposition, formDataContentDisposition("avatar", "profile.png"))
append("X-Upload-Source", "mobile-app")
}
val formData = formData {
append("user_id", "123")
append("avatar", File("profile.png").readBytes(), headers)
}
// Manual content type handling
val response = client.post("https://example.com/upload") {
setBody(MultiPartFormDataContent(formData))
// Content-Type with boundary is set automatically
}/**
* String values collection interface
*/
interface StringValues {
val caseInsensitiveName: Boolean
fun get(name: String): String?
fun getAll(name: String): List<String>?
fun names(): Set<String>
fun isEmpty(): Boolean
fun entries(): Set<Map.Entry<String, List<String>>>
fun forEach(body: (String, List<String>) -> Unit)
operator fun contains(name: String): Boolean
operator fun contains(name: String, value: String): Boolean
}
/**
* Headers collection
*/
interface Headers : StringValues {
companion object {
val Empty: Headers
fun build(block: HeadersBuilder.() -> Unit): Headers
}
}
/**
* HTTP header names constants
*/
object HttpHeaders {
const val ContentType = "Content-Type"
const val ContentLength = "Content-Length"
const val ContentDisposition = "Content-Disposition"
const val ContentEncoding = "Content-Encoding"
const val Authorization = "Authorization"
const val UserAgent = "User-Agent"
const val Accept = "Accept"
const val AcceptEncoding = "Accept-Encoding"
const val AcceptLanguage = "Accept-Language"
const val CacheControl = "Cache-Control"
const val Connection = "Connection"
const val Cookie = "Cookie"
const val SetCookie = "Set-Cookie"
}/**
* Input abstraction for different data sources
*/
interface Input : Closeable {
suspend fun readPacket(size: Int): ByteReadPacket
suspend fun readBuffer(size: Int): Buffer
suspend fun discard(max: Long): Long
suspend fun close()
}
/**
* Channel for reading bytes
*/
interface ByteReadChannel {
val availableForRead: Int
val isClosedForRead: Boolean
val isClosedForWrite: Boolean
val totalBytesRead: Long
suspend fun readByte(): Byte
suspend fun readPacket(size: Int): ByteReadPacket
suspend fun readRemaining(limit: Long = Long.MAX_VALUE): ByteReadPacket
suspend fun readBuffer(size: Int): Buffer
suspend fun discard(max: Long = Long.MAX_VALUE): Long
suspend fun cancel(cause: Throwable?)
}
/**
* Channel for writing bytes
*/
interface ByteWriteChannel {
val availableForWrite: Int
val isClosedForWrite: Boolean
val totalBytesWritten: Long
suspend fun writeByte(b: Byte)
suspend fun writePacket(packet: ByteReadPacket)
suspend fun writeBuffer(buffer: Buffer): Int
suspend fun flush()
suspend fun close(cause: Throwable?)
}Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-client-core