commonMain.io.ktor.client.request.forms.formDsl.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ktor-client-core-jvm Show documentation
Show all versions of ktor-client-core-jvm Show documentation
Ktor is a framework for quickly creating web applications in Kotlin with minimal effort.
/*
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
package io.ktor.client.request.forms
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.util.*
import io.ktor.utils.io.*
import io.ktor.utils.io.core.*
import kotlin.contracts.*
/**
* A multipart form item. Use it to build a form in client.
*
* @param key multipart name
* @param value content, could be [String], [Number], [ByteArray], [ByteReadPacket] or [InputProvider]
* @param headers part headers, note that some servers may fail if an unknown header provided
*/
public data class FormPart(val key: String, val value: T, val headers: Headers = Headers.Empty)
/**
* Builds a multipart form from [values].
*
* Example: [Upload a file](https://ktor.io/docs/request.html#upload_file).
*/
public fun formData(vararg values: FormPart<*>): List {
val result = mutableListOf()
values.forEach { (key, value, headers) ->
val partHeaders = HeadersBuilder().apply {
append(HttpHeaders.ContentDisposition, "form-data; name=${key.escapeIfNeeded()}")
appendAll(headers)
}
val part = when (value) {
is String -> PartData.FormItem(value, {}, partHeaders.build())
is Number -> PartData.FormItem(value.toString(), {}, partHeaders.build())
is ByteArray -> {
partHeaders.append(HttpHeaders.ContentLength, value.size.toString())
PartData.BinaryItem({ ByteReadPacket(value) }, {}, partHeaders.build())
}
is ByteReadPacket -> {
partHeaders.append(HttpHeaders.ContentLength, value.remaining.toString())
PartData.BinaryItem({ value.copy() }, { value.close() }, partHeaders.build())
}
is InputProvider -> {
val size = value.size
if (size != null) {
partHeaders.append(HttpHeaders.ContentLength, size.toString())
}
PartData.BinaryItem(value.block, {}, partHeaders.build())
}
is ChannelProvider -> {
val size = value.size
if (size != null) {
partHeaders.append(HttpHeaders.ContentLength, size.toString())
}
PartData.BinaryChannelItem(value.block, partHeaders.build())
}
is Input -> error("Can't use [Input] as part of form: $value. Consider using [InputProvider] instead.")
else -> error("Unknown form content type: $value")
}
result += part
}
return result
}
/**
* Build multipart form using [block] function.
*/
public fun formData(block: FormBuilder.() -> Unit): List =
formData(*FormBuilder().apply(block).build().toTypedArray())
/**
* A form builder type used in the [formData] builder function.
*/
public class FormBuilder internal constructor() {
private val parts = mutableListOf>()
/**
* Appends a pair [key]:[value] with optional [headers].
*/
@InternalAPI
public fun append(key: String, value: T, headers: Headers = Headers.Empty) {
parts += FormPart(key, value, headers)
}
/**
* Appends a pair [key]:[value] with optional [headers].
*/
public fun append(key: String, value: String, headers: Headers = Headers.Empty) {
parts += FormPart(key, value, headers)
}
/**
* Appends a pair [key]:[value] with optional [headers].
*/
public fun append(key: String, value: Number, headers: Headers = Headers.Empty) {
parts += FormPart(key, value, headers)
}
/**
* Appends a pair [key]:[value] with optional [headers].
*/
public fun append(key: String, value: ByteArray, headers: Headers = Headers.Empty) {
parts += FormPart(key, value, headers)
}
/**
* Appends a pair [key]:[value] with optional [headers].
*/
public fun append(key: String, value: InputProvider, headers: Headers = Headers.Empty) {
parts += FormPart(key, value, headers)
}
/**
* Appends a pair [key]:[InputProvider(block)] with optional [headers].
*/
public fun appendInput(key: String, headers: Headers = Headers.Empty, size: Long? = null, block: () -> Input) {
parts += FormPart(key, InputProvider(size, block), headers)
}
/**
* Appends a pair [key]:[value] with optional [headers].
*/
public fun append(key: String, value: ByteReadPacket, headers: Headers = Headers.Empty) {
parts += FormPart(key, value, headers)
}
/**
* Appends a pair [key]:[ChannelProvider] with optional [headers].
*/
public fun append(key: String, value: ChannelProvider, headers: Headers = Headers.Empty) {
parts += FormPart(key, value, headers)
}
/**
* Appends a form [part].
*/
public fun append(part: FormPart) {
parts += part
}
internal fun build(): List> = parts
}
/**
* Appends a form part with the specified [key] using [bodyBuilder] for its body.
*/
@OptIn(ExperimentalContracts::class)
public inline fun FormBuilder.append(
key: String,
headers: Headers = Headers.Empty,
size: Long? = null,
crossinline bodyBuilder: BytePacketBuilder.() -> Unit
) {
contract {
callsInPlace(bodyBuilder, InvocationKind.EXACTLY_ONCE)
}
append(FormPart(key, InputProvider(size) { buildPacket { bodyBuilder() } }, headers))
}
/**
* A reusable [Input] form entry.
*
* @property size estimate for data produced by the block or `null` if no size estimation known
* @param block: content generator
*/
public class InputProvider(public val size: Long? = null, public val block: () -> Input)
/**
* Supplies a new [ByteReadChannel].
* @property size is total amount of bytes that can be read from [ByteReadChannel] or `null` if [size] is unknown
* @param block returns a new [ByteReadChannel]
*/
public class ChannelProvider(public val size: Long? = null, public val block: () -> ByteReadChannel)
/**
* Appends a form part with the specified [key], [filename], and optional [contentType] using [bodyBuilder] for its body.
*/
@OptIn(ExperimentalContracts::class)
public fun FormBuilder.append(
key: String,
filename: String,
contentType: ContentType? = null,
size: Long? = null,
bodyBuilder: BytePacketBuilder.() -> Unit
) {
contract {
callsInPlace(bodyBuilder, InvocationKind.EXACTLY_ONCE)
}
val headersBuilder = HeadersBuilder()
headersBuilder[HttpHeaders.ContentDisposition] = "filename=${filename.escapeIfNeeded()}"
contentType?.run { headersBuilder[HttpHeaders.ContentType] = this.toString() }
val headers = headersBuilder.build()
append(key, headers, size, bodyBuilder)
}