前幾天,我們看過了 client.get("https://ktor.io/")
怎麼存取第三方 API 並取得資料。
今天,我們要來看看另一種存取第三方 API 的方式: POST
官方教學的寫法如下
val response: HttpResponse = client.post("http://localhost:8080/post") {
setBody("Body content")
}
這邊的 setBody
不只可以放一段文字,也可以放物件
val response: HttpResponse = client.post("http://localhost:8080/customer") {
contentType(ContentType.Application.Json)
setBody(Customer(3, "Jet", "Brains"))
}
這裡面的 post
實作如下
/**
* Executes an [HttpClient]'s POST request with the specified [url] and
* an optional [block] receiving an [HttpRequestBuilder] for configuring the request.
*
* Learn more from [Making requests](https://ktor.io/docs/request.html).
*/
public suspend inline fun HttpClient.post(
urlString: String,
block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse = post { url(urlString); block() }
block
裡面定義為 HttpRequestBuilder.() -> Unit
,就可以善用 HttpRequestBuilder
來協助建立我們的 HttpRequest
。
這邊的 url(urlString)
則是
public fun HttpRequestBuilder.url(urlString: String): Unit { // ktlint-disable filename no-unit-return
url.takeFrom(urlString)
}
/**
* [URLBuilder] to configure the URL for this request.
*/
public val url: URLBuilder = URLBuilder()
這邊利用 URLBuilder
來協助建立存取的網址。
除了這種寫法以外,也可以使用這種寫法
client.get {
url {
protocol = URLProtocol.HTTPS
host = "ktor.io"
path("docs/welcome.html")
}
}
這個 url
會呼叫
/**
* Executes a [block] that configures the [URLBuilder] associated to this request.
*/
public fun url(block: URLBuilder.(URLBuilder) -> Unit): Unit = url.block(url)
得到一樣的 URLBuilder
。
看完 url()
,我們繼續看 post
/**
* Executes an [HttpClient]'s POST request with the parameters configured in [block].
*
* Learn more from [Making requests](https://ktor.io/docs/request.html).
*/
public suspend inline fun HttpClient.post(block: HttpRequestBuilder.() -> Unit): HttpResponse =
post(HttpRequestBuilder().apply(block))
/**
* Executes an [HttpClient]'s POST request with the parameters configured in [builder].
*
* Learn more from [Making requests](https://ktor.io/docs/request.html).
*/
public suspend inline fun HttpClient.post(builder: HttpRequestBuilder): HttpResponse {
builder.method = HttpMethod.Post
return request(builder)
}
我們可以發現,這段跟 HttpClient.get
的做法非常相似,只是在 HttpClient.request
之前將 builder.method
改成 HttpMethod.Post
而已
這樣的結構還允許了另一種寫法
val response: HttpResponse = client.request("https://ktor.io/") {
method = HttpMethod.Post
}
直接使用 HttpClient.request
,然後抽換掉 HttpRequestBuilder.method
也可以達成一樣的效果。
接著我們看 HttpRequestBuilder.setBody()
@OptIn(InternalAPI::class)
public inline fun <reified T> HttpRequestBuilder.setBody(body: T) {
when (body) {
null -> {
this.body = NullBody
bodyType = typeInfo<T>()
}
is OutgoingContent -> {
this.body = body
bodyType = null
}
else -> {
this.body = body
bodyType = typeInfo<T>()
}
}
}
NullBody
是一個 singleton,用來簡單的代表為空的請求或者回傳
/**
* A subject of pipeline when body of HTTP message is `null`
*/
public object NullBody
接著,如果型態是 OutgoingContent
則直接設置 body
否則就建立一個 TypeInfo
物件,來記錄 body
對應的型態。
除了可以 setBody
以外,還可以用 HttpRequestBuilder.contentType
來設置 Content-Type
/**
* Set `Content-Type` header.
*/
public fun HttpMessageBuilder.contentType(type: ContentType): Unit =
headers.set(HttpHeaders.ContentType, type.toString())
ContentType.Application
也是一個 Singleton,裡面包含了各種可以用的 Content-Type
/**
* Provides a list of standard subtypes of an `application` content type.
*/
@Suppress("KDocMissingDocumentation", "unused")
public object Application {
/**
* Represents a pattern `application / *` to match any application content type.
*/
public val Any: ContentType = ContentType("application", "*")
public val Atom: ContentType = ContentType("application", "atom+xml")
public val Cbor: ContentType = ContentType("application", "cbor")
public val Json: ContentType = ContentType("application", "json")
public val HalJson: ContentType = ContentType("application", "hal+json")
public val JavaScript: ContentType = ContentType("application", "javascript")
public val OctetStream: ContentType = ContentType("application", "octet-stream")
public val Rss: ContentType = ContentType("application", "rss+xml")
public val Xml: ContentType = ContentType("application", "xml")
public val Xml_Dtd: ContentType = ContentType("application", "xml-dtd")
public val Zip: ContentType = ContentType("application", "zip")
public val GZip: ContentType = ContentType("application", "gzip")
public val FormUrlEncoded: ContentType =
ContentType("application", "x-www-form-urlencoded")
public val Pdf: ContentType = ContentType("application", "pdf")
public val Xlsx: ContentType = ContentType(
"application",
"vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
public val Docx: ContentType = ContentType(
"application",
"vnd.openxmlformats-officedocument.wordprocessingml.document"
)
public val Pptx: ContentType = ContentType(
"application",
"vnd.openxmlformats-officedocument.presentationml.presentation"
)
public val ProtoBuf: ContentType = ContentType("application", "protobuf")
public val Wasm: ContentType = ContentType("application", "wasm")
public val ProblemJson: ContentType = ContentType("application", "problem+json")
public val ProblemXml: ContentType = ContentType("application", "problem+xml")
}
這個物件可以用來協助我們更簡單的定義 header
的內容。
到今天,我們看過了如何用 Ktor 發送 POST 請求,並且看了框架背後運行的邏輯。
希望對各位讀者有幫助!