昨天我們看過了 Ktor 怎麼發送 POST 請求。不過,除了傳送各種不同 HTTP ACTION 以外,有時我們也會需要以其他形式傳送資料
下面我們來看看 Ktor 怎麼以 FORM DATA 的方式傳輸資料
Ktor 提供的函數是 submitForm
val client = HttpClient(CIO)
val response: HttpResponse = client.submitForm(
url = "http://localhost:8080/signup",
formParameters = parameters {
append("username", "JetBrains")
append("email", "example@jetbrains.com")
append("password", "foobar")
append("confirmation", "foobar")
}
我們先看 formParameters
裡面的實作
/**
* Builds a [Parameters] instance with the given [builder] function
* @param builder specifies a function to build a map
*/
public fun parameters(builder: ParametersBuilder.() -> Unit): Parameters = Parameters.build(builder)
public interface Parameters : StringValues {
public companion object {
/**
* Empty [Parameters] instance
*/
public val Empty: Parameters = EmptyParameters
/**
* Builds a [Parameters] instance with the given [builder] function
* @param builder specifies a function to build a map
*/
public inline fun build(builder: ParametersBuilder.() -> Unit): Parameters =
ParametersBuilder().apply(builder).build()
}
}
ParametersBuilder
的實作則是
@Suppress("KDocMissingDocumentation")
public class ParametersImpl(
values: Map<String, List<String>> = emptyMap()
) : Parameters, StringValuesImpl(true, values) {
override fun toString(): String = "Parameters ${entries()}"
}
這邊繼承了 StringValuesImpl
@Suppress("KDocMissingDocumentation", "DEPRECATION")
public open class StringValuesBuilderImpl(
final override val caseInsensitiveName: Boolean = false,
size: Int = 8
) : StringValuesBuilder
這個物件功能比較多,今天我們只看有使用到的 append
override fun append(name: String, value: String) {
validateValue(value)
ensureListForKey(name).add(value)
}
通過 validateValue
檢查過後,就用 ensureListForKey
確認可以加上
@Suppress("DEPRECATION")
private fun ensureListForKey(name: String): MutableList<String> {
return values[name] ?: mutableListOf<String>().also { validateName(name); values[name] = it }
}
然後利用 Kotlin Collection 的 add
加上對應的 value
我們最終就可以拿到 Parameters
物件了
看完 parameters
怎麼生成 Parameters
物件後
HttpClient.submitForm
實作如下
/**
* Makes a request containing form parameters encoded using the `x-www-form-urlencoded` format.
*
* If [encodeInQuery] is set to `true`, form parameters are sent as URL parameters using the GET request.
* Otherwise, form parameters are sent in a POST request body.
*
* Example: [Form parameters](https://ktor.io/docs/request.html#form_parameters).
*/
public suspend fun HttpClient.submitForm(
url: String,
formParameters: Parameters = Parameters.Empty,
encodeInQuery: Boolean = false,
block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse = submitForm(formParameters, encodeInQuery) {
url(url)
block()
}
/**
* Makes a request containing form parameters encoded using the `x-www-form-urlencoded` format.
*
* If [encodeInQuery] is set to `true`, form parameters are sent as URL parameters using the GET request.
* Otherwise, form parameters are sent in a POST request body.
*
* Example: [Form parameters](https://ktor.io/docs/request.html#form_parameters).
*/
public suspend inline fun HttpClient.submitForm(
formParameters: Parameters = Parameters.Empty,
encodeInQuery: Boolean = false,
block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse = request {
if (encodeInQuery) {
method = HttpMethod.Get
url.parameters.appendAll(formParameters)
} else {
method = HttpMethod.Post
setBody(FormDataContent(formParameters))
}
block()
}
這邊我們又看到熟悉的 HttpClient.request
了
如果不是 encodeInQuery
,也就是都編在 Query String 裡面的話,那麼就需要設置 Body
這邊使用 FormDataContent
/**
* [OutgoingContent] with for the `application/x-www-form-urlencoded` formatted request.
*
* Example: [Form parameters](https://ktor.io/docs/request.html#form_parameters).
*
* @param formData: data to send.
*/
public class FormDataContent(
public val formData: Parameters
) : OutgoingContent.ByteArrayContent() {
private val content = formData.formUrlEncode().toByteArray()
override val contentLength: Long = content.size.toLong()
override val contentType: ContentType = ContentType.Application.FormUrlEncoded.withCharset(Charsets.UTF_8)
override fun bytes(): ByteArray = content
}
ContentType
的部分,這邊使用 FormUrlEncoded
public val FormUrlEncoded: ContentType =
ContentType("application", "x-www-form-urlencoded")
加上 withCharset(Charsets.UTF_8)
/**
* Creates a copy of `this` type with the added charset parameter with [charset] value.
*/
public fun ContentType.withCharset(charset: Charset): ContentType =
withParameter("charset", charset.name)
這邊會將 formData
轉換變成 content
我們來看看 Parameters.formUrlEncode
/**
* Encode form parameters
*/
public fun Parameters.formUrlEncode(): String = entries()
.flatMap { e -> e.value.map { e.key to it } }
.formUrlEncode()
formUrlEncode
則是
/**
* Encode form parameters from a list of pairs
*/
public fun List<Pair<String, String?>>.formUrlEncode(): String = buildString { formUrlEncodeTo(this) }
formUrlEncodeTo
/**
* Encode form parameters from a list of pairs to the specified [out] appendable
*/
public fun List<Pair<String, String?>>.formUrlEncodeTo(out: Appendable) {
joinTo(out, "&") {
val key = it.first.encodeURLParameter(spaceToPlus = true)
if (it.second == null) {
key
} else {
val value = it.second.toString().encodeURLParameterValue()
"$key=$value"
}
}
}
到這邊,我們終於追到了實際的字串處理。
透過一連串的呼叫,formUrlEncodeTo
在最基礎負責將 Parameter
轉換成 Query String 的格式,我們就可以將 formParameters
轉換成 FormDataContent
,並且放進 body
內,之後以 POST 的方式傳遞出去。
今天我們針對怎麼以 FORM DATA 的方式傳輸資料,這件事情我們先看到這邊,希望各位讀者都有收穫!