時間過了兩週,我們也看了不少 Ktor 可以提供的各種功能。
今天我們來看看 Ktor 框架所提供的另一大塊功能:針對 HTTP Client 開發上所提供的協助。
首先我們先安裝好 client 相關套件
implementation("io.ktor:ktor-client-core:$ktor_version")
implementation("io.ktor:ktor-client-cio:$ktor_version")
然後就可以在程式內撰寫存取第三方 API 的 Client,寫法如下
val client = HttpClient(CIO)
val response: HttpResponse = client.get("https://ktor.io/")
println(response.status)
client.close()
我們來看看 Ktor 是怎麼讓存取第三方 API 如此簡單的
首先,我們來看看 CIO
public object CIO : HttpClientEngineFactory<CIOEngineConfig> {
init {
addToLoader()
}
override fun create(block: CIOEngineConfig.() -> Unit): HttpClientEngine =
CIOEngine(CIOEngineConfig().apply(block))
override fun toString(): String = "CIO"
}
這是一個 Kotlin Object,類似其他語言的 Singleton Pattern
並且這個實作了 HttpClientEngineFactory
在這個物件內的 create()
裡面,則是做了一個 CIOEngine
回傳
HttpClient
則是
@KtorDsl
public fun <T : HttpClientEngineConfig> HttpClient(
engineFactory: HttpClientEngineFactory<T>,
block: HttpClientConfig<T>.() -> Unit = {}
): HttpClient {
val config: HttpClientConfig<T> = HttpClientConfig<T>().apply(block)
val engine = engineFactory.create(config.engineConfig)
val client = HttpClient(engine, config, manageEngine = true)
// If the engine was created using factory Ktor is responsible for its lifecycle management. Otherwise user has to
// close engine by themself.
client.coroutineContext[Job]!!.invokeOnCompletion {
engine.close()
}
return client
}
看到這邊我們可以知道,這裡又是一個利用 Factory Pattern 的方式
我們在建立 HttpClient
時,如果想要抽換裡面的 Engine,只要把 CIO
這個 HttpClientEngineFactory
換掉即可。
經過一番設置之後,我們最後會拿到一個 HttpClient
物件
/**
* A multiplatform asynchronous HTTP client, which allows you to make requests and handle responses,
* extend its functionality with plugins, such as authentication, JSON serialization, and so on.
*
* You can learn how to create a configure [HttpClient] from
* [Creating and configuring a client](https://ktor.io/docs/create-client.html).
* @property engine [HttpClientEngine] used to execute network requests.
*/
@OptIn(InternalAPI::class)
public class HttpClient(
public val engine: HttpClientEngine,
private val userConfig: HttpClientConfig<out HttpClientEngineConfig> = HttpClientConfig()
) : CoroutineScope, Closeable
取得這個物件之後,我們就可以透過 get
來試圖取得第三方 API 的內容
/**
* Executes an [HttpClient]'s GET 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.get(
urlString: String,
block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse = get { url(urlString); block() }
public suspend inline fun HttpClient.get(block: HttpRequestBuilder.() -> Unit): HttpResponse =
get(HttpRequestBuilder().apply(block))
/**
* Executes an [HttpClient]'s GET request with the parameters configured in [builder].
*
* Learn more from [Making requests](https://ktor.io/docs/request.html).
*/
public suspend inline fun HttpClient.get(builder: HttpRequestBuilder): HttpResponse {
builder.method = HttpMethod.Get
return request(builder)
}
這邊往下看 request()
/**
* Executes an [HttpClient]'s request with the parameters specified using [builder].
*
* Learn more from [Making requests](https://ktor.io/docs/request.html).
*/
public suspend inline fun HttpClient.request(
builder: HttpRequestBuilder = HttpRequestBuilder()
): HttpResponse = HttpStatement(builder, this).execute()
這邊會取得一個 HttpStatement
物件
/**
* Prepared statement for a HTTP client request.
* This statement doesn't perform any network requests until [execute] method call.
* [HttpStatement] is safe to execute multiple times.
*
* Example: [Streaming data](https://ktor.io/docs/response.html#streaming)
*/
public class HttpStatement(
private val builder: HttpRequestBuilder,
@PublishedApi
internal val client: HttpClient
)
HttpStatement().execute()
則是
/**
* Executes this statement and download the response.
* After the method execution finishes, the client downloads the response body in memory and release the connection.
*
* To receive exact type, consider using [body<T>()] method.
*/
public suspend fun execute(): HttpResponse = execute {
val savedCall = it.call.save()
savedCall.response
}
這邊的 it.call
是 HttpClientCall
物件
/**
* The associated [HttpClientCall] containing both
* the underlying [HttpClientCall.request] and [HttpClientCall.response].
*/
public abstract val call: HttpClientCall
HttpClientCall.save
則是
/**
* Fetch data for [HttpClientCall] and close the origin.
*/
@OptIn(InternalAPI::class)
public suspend fun HttpClientCall.save(): HttpClientCall {
val responseBody = response.content.readRemaining().readBytes()
return SavedHttpCall(client, request, response, responseBody)
}
到這邊,我們可以粗淺地看出這段邏輯的流向
框架先建立了一個 CIO
的 Singleton,然後建立出 HttpClient
物件
這個物件搭配 HttpRequestBuilder
協助建立 HttpRequest
後
就可以送出並取得回傳內容了
今天我們先掌握概略的邏輯,明天我們來細看中間的邏輯細節。