使用場(chǎng)景
一個(gè)“樸素”的 url 完全可以用一個(gè)字符串來表示(例如 "https://www.youzan.com"
),我們可以利用 Kotlin 語言本身的特性為 String
類型添加一個(gè)擴(kuò)展函數(shù) httpGet()
朝卒,然后借此發(fā)起 http 請(qǐng)求:
"https://www.youzan.com".httpGet()
但是涵紊,對(duì)于不是樸素字符串的對(duì)象來說河泳,我們可以讓其實(shí)現(xiàn)一個(gè)接口:
interface PathStringConvertible {
val path: String
}
然后构诚,將“計(jì)算”過后的 path 通過一個(gè) String
類型提供出來徘层,例如:
enum class HttpsBin(relativePath: String) : Fuel.PathStringConvertible {
USER_AGENT("user-agent"),
POST("post"),
PUT("put"),
PATCH("patch"),
DELETE("delete");
override val path = "https://httpbin.org/$relativePath"
}
但是,也會(huì)存在一種情況吻商,所有的 url 可能會(huì)共享一個(gè) base url掏颊,或者是其他公用參數(shù),那么還需有一個(gè)地方來存儲(chǔ)這些通用配置艾帐,這個(gè)地方的幕后老大就叫 FuelManager
乌叶。
String
和 PathStringConvertible
最終也會(huì)調(diào)用到 FuelManager
。
+----------+
| String |------------->----+
+----------+ | +------+ +-------------+
|--->| Fuel |--->| FuelManager |
+-------------------------+ | +------+ +-------------+
| PathStringConvertible |->-+
+-------------------------+
除了通過 String
或者 PathStringConvertiable
來發(fā)起請(qǐng)求柒爸,我們還可以直接用一個(gè) Request
枉昏,因此 Fuel
還提供了轉(zhuǎn)換 Request
的接口:
除了通過 String
或者 PathStringConvertiable
來發(fā)起請(qǐng)求,我們還可以直接用一個(gè) Request
揍鸟,因此 Fuel
還提供了轉(zhuǎn)換 Request
的接口:
interface RequestConvertible {
val request: Request
}
綜上來看,發(fā)起一個(gè) http 請(qǐng)求可以有如下四種方式:
- 一個(gè)字符串
-
PathStringConvertible
變量 -
RequestConvertible
變量 - 直接使用
Fuel
伴生對(duì)象提供的方法
代碼實(shí)現(xiàn)
對(duì)外提供服務(wù)的 Fuel
首先 Fuel
作為對(duì)外的接口提供方(類似 Facade 模式)句旱,通過一個(gè)伴生對(duì)象(companion object)提供服務(wù)(以 get 方法為例):
companion object {
@JvmStatic @JvmOverloads
fun get(path: String, parameters: List<Pair<String, Any?>>? = null): Request =
request(Method.GET, path, parameters)
@JvmStatic @JvmOverloads
fun get(convertible: PathStringConvertible, parameters: List<Pair<String, Any?>>? = null): Request =
request(Method.GET, convertible, parameters)
private fun request(method: Method, path: String, parameters: List<Pair<String, Any?>>? = null): Request =
FuelManager.instance.request(method, path, parameters)
private fun request(method: Method, convertible: PathStringConvertible, parameters: List<Pair<String, Any?>>? = null): Request =
request(method, convertible.path, parameters)
}
Fuel 類通過伴生對(duì)象提供的 http 方法有 get/post/put/patch/delete/download/upload/head阳藻,這些方法最終會(huì)路由到 FuleManager
的實(shí)例(instance)。
同時(shí)谈撒,Fule.kt
源文件為 String
和 PathStringConvertible
定義了擴(kuò)展腥泥,以支持這些 http 方法(以 get 方法為例):
@JvmOverloads
fun String.httpGet(parameters: List<Pair<String, Any?>>? = null): Request = Fuel.get(this, parameters)
@JvmOverloads
fun Fuel.PathStringConvertible.httpGet(parameter: List<Pair<String, Any?>>? = null): Request = Fuel.get(this, parameter)
幕后老大 FuleManager
FuleManager 利用伴生對(duì)象實(shí)現(xiàn)了單例模式:
companion object {
//manager
var instance by readWriteLazy { FuelManager() }
}
同時(shí)利用代理屬性實(shí)現(xiàn)了單例的懶加載。
readWriteLazy
是一個(gè)函數(shù)啃匿,它的返回值是一個(gè) ReadWriteProperty
蛔外,代碼比較容易蛆楞,具體可見 Delegates.kt。
也就是說夹厌,當(dāng)我們第一次訪問 FuelManager
時(shí)豹爹,一個(gè)具體的實(shí)例會(huì)被創(chuàng)建出來,這個(gè)實(shí)例擔(dān)負(fù)了存儲(chǔ)公用配置和發(fā)起請(qǐng)求的重任矛纹,首先來看它的屬性:
var client: Client
var proxy: Proxy?
var basePath: String?
var baseHeaders: Map<String, String>?
var baseParams: List<Pair<String, Any?>>
var keystore: KeyStore?
var socketFactory: SSLSocketFactory
var hostnameVerifier: HostnameVerifier
Client
是一個(gè)接口臂聋,通過它我們可以自定義 http 引擎。
interface Client {
fun executeRequest(request: Request): Response
}
+---------+ +--------+ +----------+
| Request | ==> | Client | ==> | Response |
+---------+ +--------+ +----------+
|
\|/ +--------------------+
+------------+ | HttpURLConnection |
| HttpClient | --based on-- +--------------------+
+------------+ | HttpsURLConnection |
+--------------------+
Fuel 默認(rèn)提供的 Http 引擎是 HttpClient
或南,它是基于 HttpURLConnection 的實(shí)現(xiàn)孩等。
Fuel 默認(rèn)提供的 Http 引擎是 HttpClient
,它是基于 HttpURLConnection 的實(shí)現(xiàn)采够。
basePath
肄方、baseHeaders
和 baseParams
存儲(chǔ)了請(qǐng)求的公用配置,我們可以通過 FuleManager.instance
為其賦值:
FuelManager.instance.apply {
basePath = "http://httpbin.org"
baseHeaders = mapOf("Device" to "Android")
baseParams = listOf("key" to "value")
}
keystore
用于構(gòu)建 socketFactory
蹬癌,再加上 hostnameVerifier
权她,它們用于 https 請(qǐng)求,在 HttpClient
中有用到:
private fun establishConnection(request: Request): URLConnection {
val urlConnection = if (proxy != null) request.url.openConnection(proxy) else request.url.openConnection()
return if (request.url.protocol == "https") {
val conn = urlConnection as HttpsURLConnection
conn.apply {
sslSocketFactory = request.socketFactory // socketFactory
hostnameVerifier = request.hostnameVerifier // hostnameVerifier
}
} else {
urlConnection as HttpURLConnection
}
}
如果要深入了解 HTTPS 證書冀瓦,可參考 「HTTPS 精讀之 TLS 證書校驗(yàn)」伴奥。
FuelManager 在發(fā)起請(qǐng)求時(shí)會(huì)用這些參數(shù)構(gòu)建一個(gè) Request
。
fun request(method: Method, path: String, param: List<Pair<String, Any?>>? = null): Request {
val request = request(Encoding(
httpMethod = method,
urlString = path,
baseUrlString = basePath,
parameters = if (param == null) baseParams else baseParams + param
).request)
request.client = client
request.headers += baseHeaders.orEmpty()
request.socketFactory = socketFactory
request.hostnameVerifier = hostnameVerifier
request.executor = createExecutor()
request.callbackExecutor = callbackExecutor
request.requestInterceptor = requestInterceptors.foldRight({ r: Request -> r }) { f, acc -> f(acc) }
request.responseInterceptor = responseInterceptors.foldRight({ _: Request, res: Response -> res }) { f, acc -> f(acc) }
return request
}
關(guān)于 requestInterceptor
和 responseInterceptor
翼闽,原理與 OkHttp 實(shí)現(xiàn)的攔截器一致拾徙,只不過這里利用了 Kotlin 的高階函數(shù),代碼實(shí)現(xiàn)非常簡(jiǎn)單感局,具體細(xì)節(jié)可參考 「Kotlin實(shí)戰(zhàn)之Fuel的高階函數(shù)」尼啡。
跟其他網(wǎng)絡(luò)庫(kù)一樣,一次完整的請(qǐng)求询微,必然包含兩個(gè)實(shí)體—— Request
& Response
崖瞭,先來看 Request
。
請(qǐng)求實(shí)體 Request
class Request(
val method: Method,
val path: String,
val url: URL,
var type: Type = Type.REQUEST,
val headers: MutableMap<String, String> = mutableMapOf(),
val parameters: List<Pair<String, Any?>> = listOf(),
var name: String = "",
val names: MutableList<String> = mutableListOf(),
val mediaTypes: MutableList<String> = mutableListOf(),
var timeoutInMillisecond: Int = 15000,
var timeoutReadInMillisecond: Int = timeoutInMillisecond) : Fuel.RequestConvertible
它支持三種類型的請(qǐng)求:
enum class Type {
REQUEST,
DOWNLOAD,
UPLOAD
}
針對(duì)每個(gè)類型都有對(duì)應(yīng)的任務(wù)(task):
//underlying task request
internal val taskRequest: TaskRequest by lazy {
when (type) {
Type.DOWNLOAD -> DownloadTaskRequest(this)
Type.UPLOAD -> UploadTaskRequest(this)
else -> TaskRequest(this)
}
}
涉及到上傳下載的 DownloadTaskRequest
和 UploadTaskRequest
都繼承自 TaskRequest
撑毛,它們會(huì)處理文件和流相關(guān)的東西书聚,關(guān)于此可參考 IO 哥寫的 一些「流與管道」的小事 以及 OK, IO。
FuelManager
在構(gòu)造 Request
時(shí)用到了一個(gè)類——Encoding
:
class Encoding(
val httpMethod: Method,
val urlString: String,
val requestType: Request.Type = Request.Type.REQUEST,
val baseUrlString: String? = null,
val parameters: List<Pair<String, Any?>>? = null) : Fuel.RequestConvertible
Encoding
也是繼承自 Fuel.RequestConvertible
藻雌,它完成了對(duì) Request
參數(shù)的組裝編碼雌续,并產(chǎn)生了一個(gè) Request
。
Encoding
組裝 query parameter 的方式可以說賞心悅目胯杭,貼出來欣賞一下:
private fun queryFromParameters(params: List<Pair<String, Any?>>?): String = params.orEmpty()
.filterNot { it.second == null }
.map { (key, value) -> URLEncoder.encode(key, "UTF-8") to URLEncoder.encode("$value", "UTF-8") }
.joinToString("&") { (key, value) -> "$key=$value" }
請(qǐng)求返回結(jié)果 Response
class Response(
val url: URL,
val statusCode: Int = -1,
val responseMessage: String = "",
val headers: Map<String, List<String>> = emptyMap(),
val contentLength: Long = 0L,
val dataStream: InputStream = ByteArrayInputStream(ByteArray(0))
由 Response
的屬性可以看出驯杜,它所攜帶的仍然是一個(gè)流(Stream),我們先看 Response
是如何與 Request
串聯(lián)起來的做个。
Deserializable.kt
文件為 Request
定了名稱為 response
的擴(kuò)展函數(shù):
private fun <T : Any, U : Deserializable<T>> Request.response(
deserializable: U,
success: (Request, Response, T) -> Unit,
failure: (Request, Response, FuelError) -> Unit): Request {
val asyncRequest = AsyncTaskRequest(taskRequest)
asyncRequest.successCallback = { response ->
val deliverable = Result.of { deserializable.deserialize(response) }
callback {
deliverable.fold({
success(this, response, it)
}, {
failure(this, response, FuelError(it))
})
}
}
asyncRequest.failureCallback = { error, response ->
callback {
failure(this, response, error)
}
}
submit(asyncRequest)
return this
}
擴(kuò)展函數(shù) response
的參數(shù)中鸽心,deserializable
負(fù)責(zé)反序列化操作滚局,success
和 failure
用于處理請(qǐng)求結(jié)果。
Fuel 提供了兩個(gè) Deserializable
的實(shí)現(xiàn):StringDeserializer
以及 ByteArrayDeserializer
顽频,它們用于反序列化 response 的 stream藤肢。
異步請(qǐng)求
Deserializable.kt
為 Request
定義的擴(kuò)展函數(shù) response
在執(zhí)行異步操作時(shí)用到了一個(gè) AsnycTaskRequest
,其實(shí)它本身并不提供異步實(shí)現(xiàn)冲九,而是交由一個(gè) ExecutorService
去執(zhí)行谤草,而這個(gè) ExecutorService
恰由 FuelManager
定義,并在構(gòu)造 Request
時(shí)傳入給它莺奸。
FuleManager.kt
//background executor
var executor: ExecutorService by readWriteLazy {
Executors.newCachedThreadPool { command ->
Thread(command).also { thread ->
thread.priority = Thread.NORM_PRIORITY
thread.isDaemon = true
}
}
}
AsyncTaskRequest
和 UploadTaskRequest
丑孩、DownloadTaskRequest
一樣,都是繼承自 TaskRequest
灭贷,只不過它多了兩個(gè)異步調(diào)用的回調(diào):
var successCallback: ((Response) -> Unit)? = null
var failureCallback: ((FuelError, Response) -> Unit)? = null
請(qǐng)求圖例
至此温学,請(qǐng)求、回復(fù)甚疟,異步調(diào)用仗岖,對(duì)外接口都了解過了,一個(gè)基本的網(wǎng)絡(luò)庫(kù)框架已經(jīng)成型览妖。
+------------------------+
| https://www.youzan.com |
+------------------------+
|
|
\|/
+------+
| Fuel |
+------+
|
|
\|/
+-------------+
| FuelManager |
+-------------+
|
|
\|/
+---------+ +--------+ +----------+
| Request | ===> | Client | ===> | Response |
+---------+ +--------+ +----------+
雖然Fuel 的復(fù)雜度不可與 OkHttp 相提并論轧拄,但是依賴 Kotlin 語言本身的靈活性,它的代碼卻比 OkHttp 要簡(jiǎn)潔的多讽膏,特別是關(guān)于高階函數(shù)和擴(kuò)展函數(shù)的運(yùn)用檩电,極大地提升了代碼的可讀性。