CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-ktor--ktor-client-core-macosarm64

Ktor HTTP client core library providing asynchronous HTTP client functionality for multiplatform applications with macOS ARM64 support.

Pending
Overview
Eval results
Files

form-data-content.mddocs/

Form Data and Content Handling

Content handling for form data, multipart uploads, file uploads, and various content types with proper encoding and streaming support for HTTP client requests.

Capabilities

Form Data Submission

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 = {}
): HttpResponse

Usage 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\"")
        })
    }
)

FormDataContent

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")
    }))
}

MultiPartFormDataContent

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())
            })
        }
    ))
}

PartData Types

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))
}

Parameters Builder

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 Types and Utilities

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"))
            })
        }
    ))
}

Types

Form Data Types

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

docs

built-in-plugins.md

engine-configuration.md

form-data-content.md

http-client.md

index.md

plugin-system.md

request-building.md

response-handling.md

tile.json