有了 HttpClient
這個物件之後,再來就是實際的連線囉,我們這邊以 Github 的 api 為例:
GET: https://api.github.com/users/<USERNAME>
如果大家有喜歡看原始碼的習慣可能會發現整個 HttpClient
的 class 裡其實除了一個 execute
function 以外,沒有任何直接跟連線有關的程式碼,這也有點像是 Kotlin 的 convention,因為有了 extension function 的幫助,我們可以在 class 的外部寫很多 helper function 讓我們更容易使用,同時又可以保持 class 核心功能的簡潔,不過那我們要怎麼真的建立一個連線呢?
其實只要呼叫 request
就可以了,範例如下:
val response: HttpResponse = client.request("https://api.github.com/users/$userName") {
// extra config
}
點擊 request
進去會發現 request
的確就是一個 extension function,而且它實際上也是呼叫其他的 extension function:
public suspend inline fun HttpClient.request(
urlString: String,
block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse = request {
url(urlString)
block()
}
public suspend inline fun HttpClient.request(block: HttpRequestBuilder.() -> Unit): HttpResponse =
request(HttpRequestBuilder().apply(block))
public suspend inline fun HttpClient.request(
builder: HttpRequestBuilder = HttpRequestBuilder()
): HttpResponse = HttpStatement(builder, this).execute()
HttpStatement
的內部,又會再 call HttpClient
的 execute
喔,但這部分就留給讀者們自行探索囉。但 request
本身是怎麼區分 HttpMethod 是 GET 還是 POST 的呢?
在上面這一連串 call 裡面有個 HttpRequestBuilder
,讓我們點進去看一下:
public class HttpRequestBuilder : HttpMessageBuilder {
/**
* [URLBuilder] to configure the URL for this request.
*/
public val url: URLBuilder = URLBuilder()
/**
* [HttpMethod] used by this request. [HttpMethod.Get] by default.
*/
public var method: HttpMethod = HttpMethod.Get
/**
* [HeadersBuilder] to configure the headers for this request.
*/
override val headers: HeadersBuilder = HeadersBuilder()
...
由上述程式碼我們可以發現,原來 HttpMethod 預設就是 GET 啊,難怪我們不需要設定,如果要改成 POST 也可以直接在這個 lambda block 做改變:
client.request("https://<your path here>"){
// change HttpMethod
method = HttpMethod.Post
}
GET、POST 等 HttpMethod 都算是蠻常用的一些 config,所以 Ktor 其實也提供了另一系列的 extension function,可以把 request
直接換成 get
、post
就可以不需要額外設定了:
client.get("https://<your path here>")
client.post("https://<your path here>")
根據我們之前的內容,我們可以把我們的 API 改成這樣的形式:
val response: HttpResponse = client.get("https://api.github.com/users/$userName")
再來就讓我們看看 HttpResponse
是什麼,跟我們想要的結果有什麼不同!
首先,一個連線我們必須要掌握的是他的 status code,是正常、失敗還是其他的狀態,在 Ktor 只要直接呼叫 status.value
就可以了:
val response: HttpResponse = client.get("https://api.github.com/users/$userName")
if (response.status.value in 200..299) {
println("Successful response!")
}
HttpResponse
裡面也有一個 body 的 function 可以回傳連線的結果,有趣的是這個 function 的宣告:
public suspend inline fun <reified T> HttpResponse.body(): T = call.bodyNullable(typeInfo<T>()) as T
回傳的型別是 T,而這個 T 會一路帶下去做檢查以及轉換,所以使用的時候非常簡單,不管你是想要 String
還是 ByteArray
:
val response: HttpResponse = client.get("https://api.github.com/users/$userName")
val stringBody: String = response.body()
val byteArrayBody: ByteArray = response.body()
值得一提的是這個 T 也不是萬能的,畢竟你傳入一個他不知道怎麼對應的型別,它除了拋出錯誤也沒其他方式可以處理了對吧,那問題就回到怎麼讓他可以支援我們想要的型別了!