CtrlK
BlogDocsLog inGet started
Tessl Logo

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

Ktor HTTP Client Core - a multiplatform asynchronous HTTP client library for Kotlin providing comprehensive HTTP request/response handling with plugin architecture.

Pending
Overview
Eval results
Files

forms-and-uploads.mddocs/

Form Data and File Uploads

Comprehensive form submission and file upload functionality including URL-encoded forms, multipart forms, and binary data handling.

Capabilities

Form Submission Functions

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>
): HttpResponse

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

Form Data Content Classes

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

Form Data Builder

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)

PartData Sealed Class

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

Parameters Builder

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 names

Content-Type and Headers

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

Types

Form Data Types

/**
 * 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"
}

File and Input Types

/**
 * 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

docs

client-configuration.md

cookie-management.md

forms-and-uploads.md

http-caching.md

http-requests.md

index.md

plugin-system.md

response-handling.md

server-sent-events.md

websockets.md

tile.json