前言
閱讀過上一篇對(duì)網(wǎng)絡(luò)編程的概述一文后胸完,應(yīng)該對(duì)網(wǎng)絡(luò)編程有一個(gè)大體的概念了公条。從本文開始硼身,將會(huì)開始對(duì)OkHttp的源碼開始進(jìn)行解析。
OkHttp是由square開發(fā)的網(wǎng)絡(luò)請(qǐng)求哭覆享,它是當(dāng)前Android開發(fā)中使用率高達(dá)近100%的網(wǎng)絡(luò)請(qǐng)求庫(kù)佳遂。而且在Android源碼中也內(nèi)置了這個(gè)庫(kù)作為官方的網(wǎng)絡(luò)請(qǐng)求。甚至在一小部分后端也開始使用了撒顿。
關(guān)于前置知識(shí)丑罪,可以閱讀我寫的上篇OKHttp系列解析(一) Okio源碼解析 以及Android重學(xué)系列 Android網(wǎng)絡(luò)編程 總覽
正文
老規(guī)矩,先來看看OkHttp是如何使用的凤壁。
public class GetExample {
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
public static void main(String[] args) throws IOException {
GetExample example = new GetExample();
String response = example.run("https://raw.github.com/square/okhttp/master/README.md");
System.out.println(response);
}
}
就用官方的例子來看看吩屹,實(shí)際上就是通過Get請(qǐng)求github中README文本。
這個(gè)過程涉及了如下幾個(gè)重要角色:
-
OkHttpClient
Okhttp用于請(qǐng)求的執(zhí)行客戶端 -
Request
通過構(gòu)造者設(shè)計(jì)模式拧抖,構(gòu)建的一個(gè)請(qǐng)求對(duì)象 -
Call
是通過client.newCall
生成的請(qǐng)求執(zhí)行對(duì)象煤搜,當(dāng)執(zhí)行了execute之后才會(huì)真正的開始執(zhí)行網(wǎng)絡(luò)請(qǐng)求 -
Response
是通過網(wǎng)絡(luò)請(qǐng)求后,從服務(wù)器返回的信息都在里面徙鱼。內(nèi)含返回的狀態(tài)碼宅楞,以及代表響應(yīng)消息正文的ResponseBody。
再來看看POST是怎么請(qǐng)求的:
public static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
和Get很相似袱吆,只是這個(gè)過程在Request中添加了RequestBody
對(duì)象厌衙。這個(gè)RequestBody對(duì)象保存一個(gè)json字符串,并且設(shè)置MediaType為"application/json; charset=utf-8"
也就是指正文格式绞绒,可以讓客戶端識(shí)別到應(yīng)該怎么解析這串字符串婶希。
當(dāng)然還有put,delete等蓬衡,和post流程十分相似就沒有必要再展示了喻杈。
我們就以此為鋪墊來看看整個(gè)OkHttp是怎么處理網(wǎng)絡(luò)請(qǐng)求的。下面的原來來自最新的okHttp 4.1版本解析
OkHttp的概況
整個(gè)OkHttp設(shè)計(jì)的很有層次性狰晚,OKHttp把整個(gè)網(wǎng)絡(luò)請(qǐng)求邏輯拆成7個(gè)攔截器筒饰,設(shè)計(jì)成責(zé)任鏈模式的處理。我們可以把Okhttp的網(wǎng)絡(luò)請(qǐng)求大致分為如下幾層壁晒,如下圖:
- 1.retryAndFollowUpInterceptor 重試攔截器
- 2.BridgeInterceptor 建立網(wǎng)絡(luò)橋梁的攔截器瓷们,主要是為了給網(wǎng)絡(luò)請(qǐng)求時(shí)候,添加各種各種必要參數(shù)秒咐。如Cookie谬晕,Content-type
- 3.CacheInterceptor 緩存攔截器,主要是為了在網(wǎng)絡(luò)請(qǐng)求時(shí)候携取,根據(jù)返回碼處理緩存攒钳。
- 4.ConnectInterceptor 鏈接攔截器,主要是為了從鏈接池子中查找可以復(fù)用的socket鏈接雷滋。
- 5.CallServerInterceptor 真正執(zhí)行網(wǎng)絡(luò)請(qǐng)求的邏輯不撑。
- 6.Interceptor 用戶定義的攔截器文兢,在重試攔截器之前執(zhí)行
- 7.networkInterceptors 用戶定義的網(wǎng)絡(luò)攔截器,在CallServerInterceptor(執(zhí)行網(wǎng)絡(luò)請(qǐng)求攔截器)之前運(yùn)行燎孟。
每個(gè)攔截器的處理邏輯可以拆分為兩個(gè)部分禽作,請(qǐng)求部分(處理Request)可應(yīng)答部分(處理Response )。
其中最后兩點(diǎn)是允許用戶進(jìn)行自定義的揩页。本文將會(huì)專門講述前三點(diǎn)的設(shè)計(jì)和思想旷偿。
OkHttp 執(zhí)行流程
我們來看看整個(gè)流程都做了什么?
實(shí)際上OkHttp 執(zhí)行客戶端可以通過建造者模式構(gòu)建出來的爆侣,每一個(gè)成員變量代表了Okhttp中的一種能力,先來看看okhttp.Builder中的成員變量中都有寫什么萍程。
class Builder constructor() {
internal var dispatcher: Dispatcher = Dispatcher()
internal var connectionPool: ConnectionPool = ConnectionPool()
internal val interceptors: MutableList<Interceptor> = mutableListOf()
internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
internal var retryOnConnectionFailure = true
internal var authenticator: Authenticator = Authenticator.NONE
internal var followRedirects = true
internal var followSslRedirects = true
internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
internal var cache: Cache? = null
internal var dns: Dns = Dns.SYSTEM
internal var proxy: Proxy? = null
internal var proxySelector: ProxySelector? = null
internal var proxyAuthenticator: Authenticator = Authenticator.NONE
internal var socketFactory: SocketFactory = SocketFactory.getDefault()
internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
internal var x509TrustManagerOrNull: X509TrustManager? = null
internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
internal var certificateChainCleaner: CertificateChainCleaner? = null
internal var callTimeout = 0
internal var connectTimeout = 10_000
internal var readTimeout = 10_000
internal var writeTimeout = 10_000
internal var pingInterval = 0
internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
internal var routeDatabase: RouteDatabase? = null
...
}
Dispatcher
Okhttp 請(qǐng)求分發(fā)器,是整個(gè)OkhttpClient的執(zhí)行核心ConnectionPool
Okhttp鏈接池兔仰,不過會(huì)把任務(wù)委托給RealConnectionPool
處理interceptors: MutableList<Interceptor>
這是代表了okhttp所有的網(wǎng)絡(luò)攔截器networkInterceptors: MutableList<Interceptor>
這種特殊的攔截器茫负,會(huì)生效在CallServerInterceptor
真正執(zhí)行網(wǎng)絡(luò)請(qǐng)求的攔截器之前執(zhí)行。eventListenerFactory: EventListener.Factory
這是一個(gè)Event的監(jiān)聽器乎赴,可以監(jiān)聽到如callStart
開始執(zhí)行,proxySelectStart
代理選擇開始,proxySelectEnd
代理選擇結(jié)束忍法,dnsStart
dns開始查找ip映射,dnsEnd
查找ip完畢榕吼,connectStart
開始鏈接服務(wù)器饿序,secureConnectStart
開始安全鏈接服務(wù)器,secureConnectEnd
安全鏈接服務(wù)器結(jié)束,connectEnd
鏈接結(jié)束等周期authenticator: Authenticator
當(dāng)出現(xiàn)401時(shí)候羹蚣,就會(huì)使用這個(gè)對(duì)象進(jìn)行身份校驗(yàn)原探。如果配置過登錄信息,則會(huì)根據(jù)信息生成新的Request重新請(qǐng)求followRedirects
是否允許重定向cookieJar: CookieJar
這是okhttp 對(duì)cookie持久化的接口cache: Cache
這部分實(shí)際上是okhttp持久化緩存的接口dns: Dns
這部分也就是前文說過的顽素,用于通過URL地址反過來查找ip地址的對(duì)象咽弦,默認(rèn)為DnsSystem.在這里面ip地址是使用java對(duì)象InetAddress
來表示。proxy: Proxy
和ProxySelector
Okhttp中請(qǐng)求的代理對(duì)象以及自動(dòng)代理選擇器胁出。proxyAuthenticator: Authenticator
代理服務(wù)的401權(quán)限校驗(yàn)處理中心.socketFactory: SocketFactory
默認(rèn)的socket鏈接池sslSocketFactoryOrNull: SSLSocketFactory
用于https的socket鏈接工廠X509TrustManager
用于信任https證書的對(duì)象型型,編碼格式為X.509
。換句話說全蝶,就是在TLS握手期間進(jìn)行校驗(yàn)闹蒜,如果校驗(yàn)失敗則鏈接失敗connectionSpecs: List<ConnectionSpec>
ConnectionSpec實(shí)際上是用于指定http請(qǐng)求時(shí)候的socket鏈接,如果是Https則是構(gòu)建TLS鏈接時(shí)候向服務(wù)端說明的TLS版本裸诽,密碼套件的類嫂用。protocols: List<Protocol>
Okhttp支持的自定義請(qǐng)求協(xié)議集合型凳,內(nèi)含http1.1丈冬,http2.0hostnameVerifier: HostnameVerifier
。也是https中的校驗(yàn)甘畅,只是是在握手之后埂蕊,對(duì)host進(jìn)行校驗(yàn)往弓。CertificatePinner
在SSL過程鎖定證書,只允許設(shè)置在這個(gè)類中的證書才能正常的鏈接蓄氧。CertificateChainCleaner
這是一個(gè)證書鏈清理器函似。證書鏈?zhǔn)侵甘褂靡唤M收到信任的證書構(gòu)成的根證書鏈,在TLS握手過程中喉童,會(huì)清理鏈路中的證書撇寞。connectTimeout
鏈接超時(shí)時(shí)間readTimeout
讀取超時(shí)時(shí)間,writeTimeout
寫入超時(shí)時(shí)間RealWebSocket
管理Okhttp內(nèi)部所有的websocket對(duì)象RouteDatabase
這是okhttp學(xué)習(xí)那些鏈接不上去的黑名單地址堂氯,如果出現(xiàn)請(qǐng)求這些黑名單就會(huì)想辦法蔑担。如果出現(xiàn)了鏈接過程出現(xiàn)異常,之后會(huì)想辦法找到可以替代的路由咽白。
大致上我們需要聊的內(nèi)容都在這里面了啤握。有了對(duì)okhttp的大體的功能之后,我們先來看看Okhttp的運(yùn)行遠(yuǎn)離晶框。
Okhttp 分發(fā)請(qǐng)求入口 newCall
先來看看Okhttp如果需要構(gòu)造一個(gè)可以請(qǐng)求的對(duì)象需要調(diào)用如下方法
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
生成一個(gè)RealCall
對(duì)象排抬。其中第三個(gè)參數(shù)說迷宮這個(gè)請(qǐng)求是否是websocket。
得到RealCall
對(duì)象之后授段,一般有兩種選擇進(jìn)行網(wǎng)絡(luò)請(qǐng)求:
1.如官方給出的方法蹲蒲,調(diào)用excute方法,把執(zhí)行設(shè)置在RealCall的Request對(duì)象畴蒲。很少使用excute的方式悠鞍,開發(fā)中更多的下面這種方式。
2.使用方法
enqueue
把請(qǐng)求分發(fā)按照隊(duì)列方式順序消費(fèi)執(zhí)行模燥。
RealCall excute
override fun execute(): Response {
check(executed.compareAndSet(false, true)) { "Already Executed" }
timeout.enter()
callStart()
try {
client.dispatcher.executed(this)
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
private fun callStart() {
this.callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()")
eventListener.callStart(this)
}
首先回調(diào)上面設(shè)置的EventListenter的callStart方法咖祭,接著調(diào)用Dispatcher
的execute
方法,把當(dāng)前的RealCall傳入蔫骂,最后調(diào)用getResponseWithInterceptorChain 阻塞獲取從網(wǎng)絡(luò)請(qǐng)求響應(yīng)后的結(jié)果么翰,最后調(diào)用Dispatcher
的finished
方法
RealCall enqueue
override fun enqueue(responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
整個(gè)流程就很簡(jiǎn)單了,先給當(dāng)前的RealCall的executed原子類設(shè)置為true后辽旋,回調(diào)callStart方法浩嫌,最后調(diào)用把外面監(jiān)聽響應(yīng)數(shù)據(jù)的接口responseCallback
封裝成AsyncCall
后作為參數(shù)傳入 Dispatcher
的enqueue
方法。
我們主要來看enqueue
方法的設(shè)計(jì)补胚,execute
的使用場(chǎng)景確實(shí)不多码耐。
AsyncCall
先來看看AysncCall
都做了什么?
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
@Volatile var callsPerHost = AtomicInteger(0)
private set
fun reuseCallsPerHostFrom(other: AsyncCall) {
this.callsPerHost = other.callsPerHost
}
val host: String
get() = originalRequest.url.host
val request: Request
get() = originalRequest
val call: RealCall
get() = this@RealCall
/**
* Attempt to enqueue this async call on [executorService]. This will attempt to clean up
* if the executor has been shut down by reporting the call as failed.
*/
fun executeOn(executorService: ExecutorService) {
client.dispatcher.assertThreadDoesntHoldLock()
var success = false
try {
executorService.execute(this)
success = true
} catch (e: RejectedExecutionException) {
val ioException = InterruptedIOException("executor rejected")
ioException.initCause(e)
noMoreExchanges(ioException)
responseCallback.onFailure(this@RealCall, ioException)
} finally {
if (!success) {
client.dispatcher.finished(this) // This call is no longer running!
}
}
}
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
timeout.enter()
try {
val response = getResponseWithInterceptorChain()
signalledCallback = true
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
} else {
responseCallback.onFailure(this@RealCall, e)
}
} catch (t: Throwable) {
cancel()
if (!signalledCallback) {
val canceledException = IOException("canceled due to $t")
canceledException.addSuppressed(t)
responseCallback.onFailure(this@RealCall, canceledException)
}
throw t
} finally {
client.dispatcher.finished(this)
}
}
}
}
AysncCall
是一個(gè)Runnable對(duì)象溶其。一旦線程開始執(zhí)行的時(shí)候就會(huì)運(yùn)行其中的run方法骚腥。而executeOn 方法這是允許任何的線程池執(zhí)行該AsyncCall對(duì)象,接著調(diào)用其中的run
方法瓶逃。
在run方法中依次執(zhí)行了如下步驟:
- getResponseWithInterceptorChain 執(zhí)行Okhttp中所有的攔截器束铭,并獲得對(duì)象response廓块。
- 2.調(diào)用responseCallback的onResponse 方法把response對(duì)象回調(diào)出去。
- 3.如果遇到IOException異常則返回responseCallback.onFailure
- 4.其他異常則調(diào)用cancel 方法取消請(qǐng)求后契沫,回調(diào)responseCallback.onFailure
- 5.調(diào)用Dispatcher的finished 方法結(jié)束執(zhí)行带猴。
Dispatcher enqueue
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
private val runningSyncCalls = ArrayDeque<RealCall>()
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
readyAsyncCalls.add(call)
if (!call.call.forWebSocket) {
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute()
}
private fun findExistingCallWithHost(host: String): AsyncCall? {
for (existingCall in runningAsyncCalls) {
if (existingCall.host == host) return existingCall
}
for (existingCall in readyAsyncCalls) {
if (existingCall.host == host) return existingCall
}
return null
}
這里有三個(gè)隊(duì)列十分重要:
- 1.readyAsyncCalls 異步執(zhí)行的準(zhǔn)備隊(duì)列
- 2.runningAsyncCalls 正在異步執(zhí)行隊(duì)列
- 3.runningSyncCalls 正在同步執(zhí)行隊(duì)列
清楚這三個(gè)隊(duì)列的功能后,下面就很好理解了:
1.首先把當(dāng)前的
AsyncCall
添加到readyAsyncCalls 預(yù)備執(zhí)行隊(duì)列中懈万。這是一個(gè)ArrayDeque
對(duì)象拴清,這是一個(gè)雙端隊(duì)列,可以作為棧使用会通。這個(gè)隊(duì)列看起來像一個(gè)鏈表贷掖,實(shí)質(zhì)上內(nèi)部是一個(gè)數(shù)組(每一次擴(kuò)容為原來的2倍,并且初始值為8渴语,內(nèi)用head和tail標(biāo)示整個(gè)隊(duì)列的范圍苹威,只允許操作頭部和尾部).2.其次通過findExistingCallWithHost 查找是否有host相同的AsyncCall 在
runningAsyncCalls
和readyAsyncCalls
,存在則調(diào)用reuseCallsPerHostFrom
復(fù)用這個(gè)AsyncCall,也就是復(fù)用這個(gè)請(qǐng)求配置驾凶,沒必要重新構(gòu)建全新的對(duì)象牙甫。3.promoteAndExecute 開始通過線程池執(zhí)行保存在隊(duì)列中的AsyncCall
promoteAndExecute
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
}
return executorServiceOrNull!!
}
@get:Synchronized var maxRequests = 64
set(maxRequests) {
require(maxRequests >= 1) { "max < 1: $maxRequests" }
synchronized(this) {
field = maxRequests
}
promoteAndExecute()
}
@get:Synchronized var maxRequestsPerHost = 5
set(maxRequestsPerHost) {
require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
synchronized(this) {
field = maxRequestsPerHost
}
promoteAndExecute()
}
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
i.remove()
asyncCall.callsPerHost.incrementAndGet()
executableCalls.add(asyncCall)
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
return isRunning
}
SynchronousQueue
是一個(gè)單對(duì)單的消費(fèi)者生產(chǎn)者模式數(shù)據(jù)結(jié)構(gòu),必須要要有一個(gè)Request對(duì)應(yīng)一個(gè)Data模式的數(shù)據(jù)結(jié)構(gòu)调违。更多的Request會(huì)進(jìn)入阻塞等待窟哺,知道有Data來匹配。
這里面設(shè)置一個(gè)默認(rèn)的線程池技肩,其中線程的調(diào)度隊(duì)列就是通過SynchronousQueue
處理成一對(duì)一的消費(fèi)者生產(chǎn)者模式且轨。
而在一次請(qǐng)求中一次性消費(fèi)AsyncCall最大的數(shù)量默認(rèn)為64,且這個(gè)AysncCall復(fù)用Host的次數(shù)要小于5次虚婿。
最后就會(huì)添加到executableCalls和runningAsyncCalls 加入到執(zhí)行隊(duì)列中咧党。最后遍歷一次executableCalls中AsyncCall的executeOn震叮,執(zhí)行其中的run方法啦租。
那么我們需要看看整個(gè)Okhttp執(zhí)行的核心方法getResponseWithInterceptorChain
.
getResponseWithInterceptorChain
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
var calledNoMoreExchanges = false
try {
val response = chain.proceed(originalRequest)
if (isCanceled()) {
response.closeQuietly()
throw IOException("Canceled")
}
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
首先構(gòu)造一個(gè)可變的 Interceptor集合农尖。這個(gè)集合的順序其實(shí)就是指代了整個(gè)okhttp攔截器的執(zhí)行順序。
整個(gè)流程如圖:
注意如果是websocket的話剧浸,就不會(huì)執(zhí)行用戶自定定義的NetworkInterceptor锹引。
然后使用RealInterceptorChain
包裹所有的攔截器后,執(zhí)行RealInterceptorChain.proceed
方法執(zhí)行Request唆香。
RealInterceptorChain 攔截器管理器
internal fun copy(
index: Int = this.index,
exchange: Exchange? = this.exchange,
request: Request = this.request,
connectTimeoutMillis: Int = this.connectTimeoutMillis,
readTimeoutMillis: Int = this.readTimeoutMillis,
writeTimeoutMillis: Int = this.writeTimeoutMillis
) = RealInterceptorChain(call, interceptors, index, exchange, request, connectTimeoutMillis,
readTimeoutMillis, writeTimeoutMillis)
override fun proceed(request: Request): Response {
check(index < interceptors.size)
calls++
...
// Call the next interceptor in the chain.
val next = copy(index = index + 1, request = request)
val interceptor = interceptors[index]
@Suppress("USELESS_ELVIS")
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
...
return response
}
這個(gè)過程可以看到實(shí)際上又copy可一個(gè)RealInterceptorChain 對(duì)象嫌变,不過index 下標(biāo)增加了1,此時(shí)就會(huì)繼續(xù)執(zhí)行對(duì)應(yīng)下標(biāo)的Interceptor躬它。
換句話說腾啥,每當(dāng)一個(gè)攔截器走完一個(gè)Request的處理流程就會(huì)生成一個(gè)新的RealInterceptorChain并且下標(biāo)+1,時(shí)候下一個(gè)攔截器的intercept方法。不斷的迭代下去碑宴。
這種思路,在我寫的OkRxCache的庫(kù)中有使用桑谍,很實(shí)用延柠。能夠把復(fù)雜且層級(jí)結(jié)構(gòu)分明的邏輯拆分出來,做到可組裝的效果锣披。
那么就來看看第一個(gè)攔截器retryAndFollowUpInterceptor 做了什么贞间?
retryAndFollowUpInterceptor 重試攔截器
整個(gè)攔截器可以劃分為2個(gè)部分進(jìn)行理解,以方法realChain.proceed
作為分割線雹仿,上部分為請(qǐng)求邏輯增热,下部分為應(yīng)答處理邏輯
retryAndFollowUpInterceptor 處理請(qǐng)求
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
var request = chain.request
val call = realChain.call
var followUpCount = 0
var priorResponse: Response? = null
var newExchangeFinder = true
var recoveredFailures = listOf<IOException>()
while (true) {
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
var response: Response
var closeActiveExchange = true
try {
if (call.isCanceled()) {
throw IOException("Canceled")
}
try {
response = realChain.proceed(request)
newExchangeFinder = true
} catch (e: RouteException) {
if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
throw e.firstConnectException.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e.firstConnectException
}
newExchangeFinder = false
continue
} catch (e: IOException) {
if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
throw e.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e
}
newExchangeFinder = false
continue
}
...
}
}
- 1.調(diào)用RealCall的enterNetworkInterceptorExchange方法實(shí)例化一個(gè)
ExchangeFinder
在RealCall對(duì)象中。 - 2.執(zhí)行RealCall的proceed 方法胧辽,進(jìn)入下一個(gè)攔截器峻仇,進(jìn)行下一步的請(qǐng)求處理。
- 3.如果出現(xiàn)路由異常邑商,則通過recover方法校驗(yàn)摄咆,當(dāng)前的鏈接是否可以重試,不能重試則拋出異常人断,離開當(dāng)前的循環(huán)吭从。
recover 校驗(yàn)鏈接是否可以重試
private fun recover(
e: IOException,
call: RealCall,
userRequest: Request,
requestSendStarted: Boolean
): Boolean {
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure) return false
// We can't send the request body again.
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false
// This exception is fatal.
if (!isRecoverable(e, requestSendStarted)) return false
// No more routes to attempt.
if (!call.retryAfterFailure()) return false
// For failure recovery, use the same route selector with a new connection.
return true
}
1.如果Okhttp 的retryOnConnectionFailure為false,禁止重試則返回false
2.requestIsOneShot 如果requestIsOneShot校驗(yàn)的是RequestBody的isOneShot是否是true, isOneShot默認(rèn)是false恶迈。說明一個(gè)請(qǐng)求正文可以多次請(qǐng)求(多次請(qǐng)求的情況如
408 客戶端超時(shí)
;401和407 權(quán)限異成穑可以通過頭部進(jìn)行滿足
;503 服務(wù)端異常,但是頭部的retry-After為0可以進(jìn)行重試
)暇仲。3.isRecoverable 校驗(yàn)當(dāng)前的異常是否是可恢復(fù)的異常步做。
ProtocolException
協(xié)議異常返回false;InterruptedIOException
io讀寫異常同時(shí)是socket鏈接超時(shí)異衬胃剑可以重試辆床;SSLHandshakeException
https握手時(shí)候的異常同時(shí)是校驗(yàn)異常CertificateException
會(huì)返回false;SSLPeerUnverifiedException
證書校驗(yàn)異常則返回false桅狠。
retryAndFollowUpInterceptor 處理應(yīng)答
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build()
}
val exchange = call.interceptorScopedExchange
val followUp = followUpRequest(response, exchange)
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
call.timeoutEarlyExit()
}
closeActiveExchange = false
return response
}
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
closeActiveExchange = false
return response
}
response.body?.closeQuietly()
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
request = followUp
priorResponse = response
} finally {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
- 1.每一次循環(huán)都會(huì)獲取上次應(yīng)答數(shù)據(jù)作為本次重定向或者權(quán)限詢問的參數(shù)讼载。
- 2.followUpRequest 根據(jù)當(dāng)前的響應(yīng)體,更新請(qǐng)求體中的內(nèi)容中跌。
- 3.如果當(dāng)前的仇視次數(shù)超過了20次咨堤,就會(huì)拋出異常,跳出循環(huán)漩符。
private const val MAX_FOLLOW_UPS = 20
其實(shí)整個(gè)重試攔截器最為核心的內(nèi)容就是followUpRequest
方法一喘。
followUpRequest 根據(jù)應(yīng)答重試請(qǐng)求處理
private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
val route = exchange?.connection?.route()
val responseCode = userResponse.code
val method = userResponse.request.method
when (responseCode) {
HTTP_PROXY_AUTH -> {
val selectedProxy = route!!.proxy
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy")
}
return client.proxyAuthenticator.authenticate(route, userResponse)
}
HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse)
HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
return buildRedirectRequest(userResponse, method)
}
HTTP_CLIENT_TIMEOUT -> {
// 408's are rare in practice, but some servers like HAProxy use this response code. The
// spec says that we may repeat the request without modifications. Modern browsers also
// repeat the request (even non-idempotent ones.)
if (!client.retryOnConnectionFailure) {
// The application layer has directed us not to retry the request.
return null
}
val requestBody = userResponse.request.body
if (requestBody != null && requestBody.isOneShot()) {
return null
}
val priorResponse = userResponse.priorResponse
if (priorResponse != null && priorResponse.code == HTTP_CLIENT_TIMEOUT) {
// We attempted to retry and got another timeout. Give up.
return null
}
if (retryAfter(userResponse, 0) > 0) {
return null
}
return userResponse.request
}
HTTP_UNAVAILABLE -> {
val priorResponse = userResponse.priorResponse
if (priorResponse != null && priorResponse.code == HTTP_UNAVAILABLE) {
// We attempted to retry and got another timeout. Give up.
return null
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
// specifically received an instruction to retry without delay
return userResponse.request
}
return null
}
HTTP_MISDIRECTED_REQUEST -> {
// OkHttp can coalesce HTTP/2 connections even if the domain names are different. See
// RealConnection.isEligible(). If we attempted this and the server returned HTTP 421, then
// we can retry on a different connection.
val requestBody = userResponse.request.body
if (requestBody != null && requestBody.isOneShot()) {
return null
}
if (exchange == null || !exchange.isCoalescedConnection) {
return null
}
exchange.connection.noCoalescedConnections()
return userResponse.request
}
else -> return null
}
}
這個(gè)過程處理了幾個(gè)HttpCode狀態(tài)碼:
public static final int HTTP_PROXY_AUTH = 407;
public static final int HTTP_UNAUTHORIZED = 401;
public static final int HTTP_CLIENT_TIMEOUT = 408;
public static final int HTTP_UNAVAILABLE = 503;
const val HTTP_TEMP_REDIRECT = 307
const val HTTP_PERM_REDIRECT = 308
const val HTTP_MISDIRECTED_REQUEST = 421
下面我們一個(gè)個(gè)的解析:
狀態(tài)碼407 代理需要校驗(yàn)身份
val selectedProxy = route!!.proxy
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy")
}
return client.proxyAuthenticator.authenticate(route, userResponse)
如果狀態(tài)代碼是407,此時(shí)會(huì)校驗(yàn)當(dāng)前路由的代理模式是不是Http協(xié)議,不是則拋出異常凸克,沒有使用代理時(shí)候接受到了407议蟆。如果是,則通過Authenticator
對(duì)響應(yīng)體進(jìn)行校驗(yàn)萎战。
此時(shí)默認(rèn)是設(shè)置沒有任何行為的代理權(quán)限校驗(yàn)器咐容。如果設(shè)置了就會(huì)調(diào)用Authenticator進(jìn)行校驗(yàn),這里面可以設(shè)置根據(jù)路由和響應(yīng)體獲取到對(duì)應(yīng)的校驗(yàn)處理蚂维〈亮#可以來看看OkHttp內(nèi)置的一個(gè)JavaNetAuthenticator
做了什么?
@Throws(IOException::class)
override fun authenticate(route: Route?, response: Response): Request? {
val challenges = response.challenges()
val request = response.request
val url = request.url
val proxyAuthorization = response.code == 407
val proxy = route?.proxy ?: Proxy.NO_PROXY
for (challenge in challenges) {
if (!"Basic".equals(challenge.scheme, ignoreCase = true)) {
continue
}
val dns = route?.address?.dns ?: defaultDns
val auth = if (proxyAuthorization) {
val proxyAddress = proxy.address() as InetSocketAddress
Authenticator.requestPasswordAuthentication(
proxyAddress.hostName,
proxy.connectToInetAddress(url, dns),
proxyAddress.port,
url.scheme,
challenge.realm,
challenge.scheme,
url.toUrl(),
Authenticator.RequestorType.PROXY
)
} else {
Authenticator.requestPasswordAuthentication(
url.host,
proxy.connectToInetAddress(url, dns),
url.port,
url.scheme,
challenge.realm,
challenge.scheme,
url.toUrl(),
Authenticator.RequestorType.SERVER
)
}
if (auth != null) {
val credentialHeader = if (proxyAuthorization) "Proxy-Authorization" else "Authorization"
val credential = Credentials.basic(
auth.userName, String(auth.password), challenge.charset)
return request.newBuilder()
.header(credentialHeader, credential)
.build()
}
}
return null // No challenges were satisfied!
}
核心其實(shí)很簡(jiǎn)單虫啥,就是通過Authenticator.requestPasswordAuthentication
獲得一個(gè)PasswordAuthentication
對(duì)象蔚约。從這個(gè)對(duì)象中獲取對(duì)應(yīng)代理設(shè)置的權(quán)限賬號(hào)密碼,并且設(shè)置到Authorization
或者Proxy-Authorization
頭部key中涂籽,重試時(shí)候會(huì)帶上這個(gè)頭部放到新的請(qǐng)求中苹祟。
狀態(tài)碼 401 請(qǐng)求要求用戶的身份認(rèn)證
return client.authenticator.authenticate(route, userResponse)
說明此時(shí)需要通過Authenticator校驗(yàn)身份,可以發(fā)送自己的用戶名和密碼過去進(jìn)行校驗(yàn)评雌。
狀態(tài)碼300苔咪,301,302柳骄,303团赏,307,308
return buildRedirectRequest(userResponse, method)
狀態(tài)碼30X 系列一般是發(fā)生了資源變動(dòng)處理的行為耐薯。如重定向跳轉(zhuǎn)等舔清。
- 300 是指有多種選擇。請(qǐng)求的資源包含多個(gè)位置
- 301 請(qǐng)求的資源已經(jīng)永久移動(dòng)了 會(huì)自動(dòng)重定向
- 302 臨時(shí)移動(dòng)曲初,資源是臨時(shí)轉(zhuǎn)移了体谒,客戶端可以沿用原來的url
- 303 查看其他地址,可301類似
- 307 臨時(shí)重定向臼婆,GET請(qǐng)求的重定向
- 308 和307類似也是臨時(shí)重定向
private fun buildRedirectRequest(userResponse: Response, method: String): Request? {
if (!client.followRedirects) return null
val location = userResponse.header("Location") ?: return null
val url = userResponse.request.url.resolve(location) ?: return null
val sameScheme = url.scheme == userResponse.request.url.scheme
if (!sameScheme && !client.followSslRedirects) return null
val requestBuilder = userResponse.request.newBuilder()
if (HttpMethod.permitsRequestBody(method)) {
val responseCode = userResponse.code
val maintainBody = HttpMethod.redirectsWithBody(method) ||
responseCode == HTTP_PERM_REDIRECT ||
responseCode == HTTP_TEMP_REDIRECT
if (HttpMethod.redirectsToGet(method) && responseCode != HTTP_PERM_REDIRECT && responseCode != HTTP_TEMP_REDIRECT) {
requestBuilder.method("GET", null)
} else {
val requestBody = if (maintainBody) userResponse.request.body else null
requestBuilder.method(method, requestBody)
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding")
requestBuilder.removeHeader("Content-Length")
requestBuilder.removeHeader("Content-Type")
}
}
if (!userResponse.request.url.canReuseConnectionFor(url)) {
requestBuilder.removeHeader("Authorization")
}
return requestBuilder.url(url).build()
}
1.先從響應(yīng)頭抒痒,取出
Location
key對(duì)應(yīng)的值,而這個(gè)值就是重定向之后的url路徑颁褂。-
2.并且校驗(yàn)這個(gè)請(qǐng)求是否是
GET
或者HEAD
.- 2.1.如果不是故响,則請(qǐng)求方式不是
PROPFIND
且狀態(tài)碼不是307
或者308
狀態(tài)碼,則強(qiáng)制設(shè)置請(qǐng)求方式為GET
- 2.2.否則則判斷請(qǐng)求狀態(tài)碼是
307
或者308
,或者請(qǐng)求方式是PROPFIND
,那么繼承之前設(shè)置的請(qǐng)求體颁独。
- 2.1.如果不是故响,則請(qǐng)求方式不是
3.不是
307
或者308
且不是PROPFIND
彩届,則清掉Header中的Content-Length
,Transfer-Encoding
,Content-Type
。4.如果之前的請(qǐng)求和本次請(qǐng)求的host(主機(jī))和port(端口)一致,則不需要
Authorization
校驗(yàn)身份誓酒。
狀態(tài)碼408 服務(wù)器等待客戶端發(fā)送請(qǐng)求超時(shí)處理
if (!client.retryOnConnectionFailure) {
return null
}
val requestBody = userResponse.request.body
if (requestBody != null && requestBody.isOneShot()) {
return null
}
val priorResponse = userResponse.priorResponse
if (priorResponse != null && priorResponse.code == HTTP_CLIENT_TIMEOUT) {
return null
}
if (retryAfter(userResponse, 0) > 0) {
return null
}
return userResponse.request
狀態(tài)碼408不常見樟蠕,但是在HAProxy中會(huì)比較常見。這種情況說明我們可以重復(fù)的進(jìn)行沒有修改過的請(qǐng)求(甚至是非冪等請(qǐng)求)。
HAProxy 是一種高可用寨辩,負(fù)載均衡的吓懈,基于TCP和HTTP的應(yīng)用程序代理。十分合適負(fù)載十分大的服務(wù)器靡狞。如github耻警,stackflow等都集成了。它實(shí)現(xiàn)了事件驅(qū)動(dòng)耍攘,單一進(jìn)程模型支持十分大的(超越一個(gè)進(jìn)程因內(nèi)存模型而限制的線程數(shù)目)。
實(shí)際上還是那老一套的畔勤,基于系統(tǒng)調(diào)用epoll蕾各,select,poll等實(shí)現(xiàn)。
這一段代碼的邏輯校驗(yàn)了如下邏輯:
- 1.當(dāng)前okhttp是否允許重試
- 2.請(qǐng)求體是否允許重復(fù)發(fā)送
- 3.是否已經(jīng)重試了庆揪,且重試的狀態(tài)是否還是408
- 4.通過retryAfter獲取響應(yīng)頭部信息
Retry-After
(頭部存在該key式曲,則設(shè)置為key的內(nèi)容否則設(shè)置為0.不存在該key設(shè)置為INT的最大數(shù)值)。拿到重試時(shí)間后判斷是否大于0缸榛,大于0說明此時(shí)返回一個(gè)空的請(qǐng)求對(duì)象吝羞,Okhttp將不會(huì)處理拋給業(yè)務(wù)層自己處理。
狀態(tài)碼 503 由于服務(wù)器的異常導(dǎo)致無法完成客戶端的請(qǐng)求
val priorResponse = userResponse.priorResponse
if (priorResponse != null && priorResponse.code == HTTP_UNAVAILABLE) {
return null
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
return userResponse.request
}
return null
如果上一次的請(qǐng)求已經(jīng)是503了内颗,就沒必要重復(fù)請(qǐng)求了钧排。且如果Retry-After
設(shè)置為0,說明需要立即重復(fù)請(qǐng)求均澳,才會(huì)重新請(qǐng)求恨溜,其他情況下只會(huì)放棄請(qǐng)求。
狀態(tài)碼421 超出了服務(wù)器最大連接數(shù)找前,需要重新請(qǐng)求
val requestBody = userResponse.request.body
if (requestBody != null && requestBody.isOneShot()) {
return null
}
if (exchange == null || !exchange.isCoalescedConnection) {
return null
}
exchange.connection.noCoalescedConnections()
return userResponse.request
}
這種情況下糟袁,只要requestBody允許重復(fù)發(fā)送,且host發(fā)生了變化躺盛,則重新返回請(qǐng)求體重新請(qǐng)求项戴。這種情況下,一般是指Http 2.0協(xié)議槽惫。這種情況下周叮,只要鏈接鏈接的得失同一個(gè)服務(wù)器,且RealConntection是合法的界斜,如果服務(wù)器返回了421狀態(tài)碼则吟,可以復(fù)用這個(gè)流。
BridgeInterceptor 橋接攔截器
BridgeInterceptor 處理請(qǐng)求體頭部
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val userRequest = chain.request()
val requestBuilder = userRequest.newBuilder()
val body = userRequest.body
if (body != null) {
val contentType = body.contentType()
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString())
}
val contentLength = body.contentLength()
if (contentLength != -1L) {
requestBuilder.header("Content-Length", contentLength.toString())
requestBuilder.removeHeader("Transfer-Encoding")
} else {
requestBuilder.header("Transfer-Encoding", "chunked")
requestBuilder.removeHeader("Content-Length")
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", userRequest.url.toHostHeader())
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive")
}
var transparentGzip = false
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true
requestBuilder.header("Accept-Encoding", "gzip")
}
val cookies = cookieJar.loadForRequest(userRequest.url)
if (cookies.isNotEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies))
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", userAgent)
}
val networkResponse = chain.proceed(requestBuilder.build())
...
}
在請(qǐng)求到下一個(gè)攔截器之前锄蹂,做了如下的事情:
- 1.設(shè)置頭部的
Content-Type
.說明內(nèi)容類型是什么 - 2.如果contentLength大于等于0氓仲,則設(shè)置頭部的
Content-Length
(說明內(nèi)容大小是多少);否則設(shè)置頭部的Transfer-Encoding
為chunked
(說明傳輸編碼為分塊傳輸) - 3.如果
Host
不存在,設(shè)置頭部的Host
(在Http 1.1之后出現(xiàn)敬扛,可以通過同一個(gè)URL訪問到不同主機(jī)晰洒,從而實(shí)現(xiàn)服務(wù)器虛擬服務(wù)器的負(fù)載均衡。如果1.1之后不設(shè)置就會(huì)返回404)啥箭。 - 4.如果
Connection
不存在谍珊,設(shè)置頭部的Connection
為Keep-Alive
(代表鏈接狀態(tài)需要保持活躍) - 5.如果
Accept-Encoding
且Range
為空,則強(qiáng)制設(shè)置Accept-Encoding
為gzip
(說明請(qǐng)求將會(huì)以gzip方式壓縮) - 6.從
CookieJar
的緩存中取出cookie設(shè)置到頭部的Cookie
- 7.如果
User-Agent
為空急侥,則設(shè)置User-Agent
到頭部
Cookie是什么砌滞?Cookie是http協(xié)議中用于追蹤用戶會(huì)話的機(jī)制。注意Http協(xié)議是無狀態(tài)的(但是底層構(gòu)成http協(xié)議的tcp協(xié)議是有狀態(tài)用于控制數(shù)據(jù)的正確性)坏怪。一個(gè)用戶所有的請(qǐng)求都是同屬一個(gè)會(huì)話贝润。在http協(xié)議中,服務(wù)器為了得知客戶端的身份會(huì)給每一個(gè)客戶端分配一個(gè)cookie铝宵,同時(shí)在服務(wù)器有一個(gè)sessionID進(jìn)行對(duì)應(yīng)打掘。都會(huì)保存在服務(wù)器的Map中。正是有這個(gè)上下文才會(huì)正確的知道該用戶的的請(qǐng)求狀態(tài)鹏秋。
BridgeInterceptor 處理響應(yīng)體
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
val strippedHeaders = networkResponse.headers.newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build()
responseBuilder.headers(strippedHeaders)
val contentType = networkResponse.header("Content-Type")
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
return responseBuilder.build()
- 1.讀取響應(yīng)頭上的信息尊蚁,把cookie'保存到CookieJar中
- 2.如果此時(shí)是
Content-Encoding
是gzip的內(nèi)容壓縮格式,則獲取當(dāng)前的響應(yīng)體的內(nèi)容侣夷,并根據(jù)Content-Type
把壓縮格式還原横朋,重新設(shè)置到響應(yīng)體中。
CacheInterceptor 緩存攔截器
CacheInterceptor 緩存攔截器處理請(qǐng)求
override fun intercept(chain: Interceptor.Chain): Response {
val call = chain.call()
val cacheCandidate = cache?.get(chain.request())
val now = System.currentTimeMillis()
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
val networkRequest = strategy.networkRequest
val cacheResponse = strategy.cacheResponse
cache?.trackResponse(strategy)
val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE
if (cacheCandidate != null && cacheResponse == null) {
// The cache candidate wasn't applicable. Close it.
cacheCandidate.body?.closeQuietly()
}
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(HTTP_GATEWAY_TIMEOUT)
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build().also {
listener.satisfactionFailure(call, it)
}
}
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build().also {
listener.cacheHit(call, it)
}
}
if (cacheResponse != null) {
listener.cacheConditionalHit(call, cacheResponse)
} else if (cache != null) {
listener.cacheMiss(call)
}
var networkResponse: Response? = null
try {
networkResponse = chain.proceed(networkRequest)
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
cacheCandidate.body?.closeQuietly()
}
}
...
}
- 1.先根據(jù)請(qǐng)求體的狀態(tài)通過CacheStrategy獲取到是否需要緩存百拓,從而獲得本次網(wǎng)絡(luò)請(qǐng)求的請(qǐng)求體以及緩存的響應(yīng)體叶撒,關(guān)鍵就是
val cacheCandidate = cache?.get(chain.request())
CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
2.如果兩者都獲取不到,此時(shí)會(huì)返回協(xié)議http 1.1耐版,錯(cuò)誤代碼為
504
祠够,消息是禁止了網(wǎng)絡(luò)請(qǐng)求但是緩存響應(yīng)不存在的應(yīng)答消息體。3.如果只是網(wǎng)絡(luò)請(qǐng)求不存在粪牲,而緩存的響應(yīng)體存在古瓤,則根據(jù)緩存的響應(yīng)構(gòu)造出響應(yīng)體對(duì)象,返回cacheHit會(huì)愛到并返回腺阳。
4.如果網(wǎng)絡(luò)請(qǐng)求存在落君,則正常的進(jìn)入下一個(gè)攔截器中。
Cache get 從LRUCache中獲取緩存
@JvmStatic
fun key(url: HttpUrl): String = url.toString().encodeUtf8().md5().hex()
internal fun get(request: Request): Response? {
val key = key(request.url)
val snapshot: DiskLruCache.Snapshot = try {
cache[key] ?: return null
} catch (_: IOException) {
return null // Give up because the cache cannot be read.
}
val entry: Entry = try {
Entry(snapshot.getSource(ENTRY_METADATA))
} catch (_: IOException) {
snapshot.closeQuietly()
return null
}
val response = entry.response(snapshot)
if (!entry.matches(request, response)) {
response.body?.closeQuietly()
return null
}
return response
}
1.首先把url路徑轉(zhuǎn)化成utf-8亭引,并且md5一下拿到摘要绎速,并調(diào)用hex獲取摘要的16進(jìn)制的字符串,這個(gè)字符串就是LRUCache的key
2.通過key拿到Cache中DiskLruCache.Snapshot對(duì)象焙蚓,每一個(gè)Snapshot就是LRUCache的緩存單位纹冤。每一個(gè)緩存單位中都緩存一個(gè)文件洒宝,在cache的get操作方法重寫中,會(huì)讀取數(shù)據(jù)為Okio的Source
operator fun get(key: String): Snapshot? {
initialize()
checkNotClosed()
validateKey(key)
val entry = lruEntries[key] ?: return null
val snapshot = entry.snapshot() ?: return null
redundantOpCount++
journalWriter!!.writeUtf8(READ)
.writeByte(' '.toInt())
.writeUtf8(key)
.writeByte('\n'.toInt())
if (journalRebuildRequired()) {
cleanupQueue.schedule(cleanupTask)
}
return snapshot
}
- 3.把數(shù)據(jù)轉(zhuǎn)化為Respone 響應(yīng)體
CacheStrategy compute 計(jì)算獲取請(qǐng)求對(duì)應(yīng)的緩存
fun compute(): CacheStrategy {
val candidate = computeCandidate()
// We're forbidden from using the network and the cache is insufficient.
if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
return CacheStrategy(null, null)
}
return candidate
}
/** Returns a strategy to use assuming the request can use the network. */
private fun computeCandidate(): CacheStrategy {
// No cached response.
if (cacheResponse == null) {
return CacheStrategy(request, null)
}
// Drop the cached response if it's missing a required handshake.
if (request.isHttps && cacheResponse.handshake == null) {
return CacheStrategy(request, null)
}
if (!isCacheable(cacheResponse, request)) {
return CacheStrategy(request, null)
}
val requestCaching = request.cacheControl
if (requestCaching.noCache || hasConditions(request)) {
return CacheStrategy(request, null)
}
val responseCaching = cacheResponse.cacheControl
val ageMillis = cacheResponseAge()
var freshMillis = computeFreshnessLifetime()
if (requestCaching.maxAgeSeconds != -1) {
freshMillis = minOf(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds.toLong()))
}
var minFreshMillis: Long = 0
if (requestCaching.minFreshSeconds != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
}
var maxStaleMillis: Long = 0
if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
}
if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
val builder = cacheResponse.newBuilder()
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"")
}
val oneDayMillis = 24 * 60 * 60 * 1000L
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"")
}
return CacheStrategy(null, builder.build())
}
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
val conditionName: String
val conditionValue: String?
when {
etag != null -> {
conditionName = "If-None-Match"
conditionValue = etag
}
lastModified != null -> {
conditionName = "If-Modified-Since"
conditionValue = lastModifiedString
}
servedDate != null -> {
conditionName = "If-Modified-Since"
conditionValue = servedDateString
}
else -> return CacheStrategy(request, null) // No condition! Make a regular request.
}
val conditionalRequestHeaders = request.headers.newBuilder()
conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)
val conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build()
return CacheStrategy(conditionalRequest, cacheResponse)
}
從LRUCache中獲取到緩存的應(yīng)答數(shù)據(jù)萌京,將會(huì)做如下的Header的校驗(yàn)雁歌。
先來看看CacheStagty的初始化:
init {
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis
val headers = cacheResponse.headers
for (i in 0 until headers.size) {
val fieldName = headers.name(i)
val value = headers.value(i)
when {
fieldName.equals("Date", ignoreCase = true) -> {
servedDate = value.toHttpDateOrNull()
servedDateString = value
}
fieldName.equals("Expires", ignoreCase = true) -> {
expires = value.toHttpDateOrNull()
}
fieldName.equals("Last-Modified", ignoreCase = true) -> {
lastModified = value.toHttpDateOrNull()
lastModifiedString = value
}
fieldName.equals("ETag", ignoreCase = true) -> {
etag = value
}
fieldName.equals("Age", ignoreCase = true) -> {
ageSeconds = value.toNonNegativeInt(-1)
}
}
}
}
}
分別取出了緩存頭部中的:
- 1.Date 緩存應(yīng)答的發(fā)送到客戶端的時(shí)間
- 2.Expires 代表應(yīng)答可以存活時(shí)間
- 3.Last-Modified 代表服務(wù)器用來校驗(yàn)客戶端的請(qǐng)求是否是最新的時(shí)間,不是則返回304
- 4.ETag 用于記錄當(dāng)前請(qǐng)求頁(yè)面狀態(tài)時(shí)效的token
- 5.Age 代理服務(wù)器用自己去緩存應(yīng)答的時(shí)候知残,該頭部代表從誕生到現(xiàn)在多長(zhǎng)時(shí)間
獲得這些基礎(chǔ)數(shù)據(jù)后靠瞎,上面的compute就是根據(jù)這5個(gè)標(biāo)志位進(jìn)行計(jì)算。
- 1.cacheResponseAge 計(jì)算這個(gè)緩存真正緩存時(shí)間方式:
接受消耗的時(shí)間 = max(Response抵達(dá)客戶端的時(shí)間 - Response從服務(wù)端發(fā)出的時(shí)間(Date字段),Age字段,0)
Response來回時(shí)間 = Response抵達(dá)客戶端的時(shí)間 - 客戶端發(fā)送的時(shí)間
緩存時(shí)間 = 當(dāng)前時(shí)間 - Response抵達(dá)客戶端的時(shí)間
當(dāng)前應(yīng)答真實(shí)緩存時(shí)間 = 接受消耗的時(shí)間 + Response來回時(shí)間 + 緩存時(shí)間
- 2.computeFreshnessLifetime 遵循如下的邏輯計(jì)算緩存有效性時(shí)間段
優(yōu)先取出CacheControl的maxAge 字段求妹,存在則返回
其次取出expires 字段乏盐,expires - (Date字段 或者 Response抵達(dá)客戶端時(shí)間)
最后取出lastModified字段, (Date字段 或者 Response抵達(dá)客戶端時(shí)間) - lastModified(上次修改)
這樣計(jì)算差值就能大致獲得這個(gè)緩存比較精確的有效時(shí)間制恍。
- 3.cacheResponseAge 獲取到的緩存的時(shí)間和 computeFreshnessLifetime計(jì)算出來的緩存時(shí)效性父能,以及在okhttp設(shè)置的最小緩存minFreshSeconds時(shí)效,通過下面簡(jiǎn)單的計(jì)算:
cacheResponseAge + minFreshSeconds > computeFreshnessLifetime
就能知道當(dāng)前緩存是否有效吧趣,從而決定是否返回一個(gè)CacheResponse上去法竞。
CacheInterceptor 緩存攔截器進(jìn)行網(wǎng)絡(luò)請(qǐng)求后的處理
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
val response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers, networkResponse.headers))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis)
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
networkResponse.body!!.close()
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache!!.trackConditionalCacheHit()
cache.update(cacheResponse, response)
return response.also {
listener.cacheHit(call, it)
}
} else {
cacheResponse.body?.closeQuietly()
}
}
val response = networkResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
if (cache != null) {
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
val cacheRequest = cache.put(response)
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
// This will log a conditional cache miss only.
listener.cacheMiss(call)
}
}
}
if (HttpMethod.invalidatesCache(networkRequest.method)) {
try {
cache.remove(networkRequest)
} catch (_: IOException) {
// The cache cannot be written.
}
}
}
return response
1.如果當(dāng)前緩存響應(yīng)體存在耙厚,且當(dāng)前網(wǎng)絡(luò)請(qǐng)求返回的狀態(tài)代碼為
304
强挫。在http協(xié)議中說明此時(shí)請(qǐng)求沒有發(fā)生變化,那么就會(huì)根據(jù)緩存響應(yīng)體構(gòu)造一個(gè)全新的響應(yīng)體薛躬,并更新當(dāng)前的是時(shí)間戳俯渤,最后更新了到緩存中,并返回到上層型宝。2.不為
304
情況八匠,且緩存策略是允許刷新的,還是會(huì)把當(dāng)前成功的響應(yīng)體保存到Cache中最后返回趴酣。
來看看如何判斷那些響應(yīng)體可以進(jìn)行緩存:
fun isCacheable(response: Response, request: Request): Boolean {
// Always go to network for uncacheable response codes (RFC 7231 section 6.1), This
// implementation doesn't support caching partial content.
when (response.code) {
HTTP_OK,
HTTP_NOT_AUTHORITATIVE,
HTTP_NO_CONTENT,
HTTP_MULT_CHOICE,
HTTP_MOVED_PERM,
HTTP_NOT_FOUND,
HTTP_BAD_METHOD,
HTTP_GONE,
HTTP_REQ_TOO_LONG,
HTTP_NOT_IMPLEMENTED,
StatusLine.HTTP_PERM_REDIRECT -> {
// These codes can be cached unless headers forbid it.
}
HTTP_MOVED_TEMP,
StatusLine.HTTP_TEMP_REDIRECT -> {
// These codes can only be cached with the right response headers.
// http://tools.ietf.org/html/rfc7234#section-3
// s-maxage is not checked because OkHttp is a private cache that should ignore s-maxage.
if (response.header("Expires") == null &&
response.cacheControl.maxAgeSeconds == -1 &&
!response.cacheControl.isPublic &&
!response.cacheControl.isPrivate) {
return false
}
}
else -> {
// All other codes cannot be cached.
return false
}
}
// A 'no-store' directive on request or response prevents the response from being cached.
return !response.cacheControl.noStore && !request.cacheControl.noStore
}
}
- 1.302,307狀態(tài)碼狀態(tài)下梨树,需要判斷獲取頭部中的
Expires
數(shù)值,獲取不存在岖寞。并且Cache-Control
頭部中max-age
為-1抡四,且public
和private
都存在的時(shí)候。
Expires和max-age 說明了有效期為無限長(zhǎng)仗谆,public說明http通信的過程中指巡,包括請(qǐng)求的發(fā)起方、代理緩存服務(wù)器都可以進(jìn)行緩存隶垮。private說明http通信過程中只有請(qǐng)求方可以緩存藻雪。
2.如果狀態(tài)碼為200,203狸吞,204勉耀,300指煎,301,404瑰排,405贯要,410,414椭住,501崇渗,308都可以緩存。
2.其他情況都返回false京郑,不允許緩存
總結(jié)
到這里就完成了對(duì)Okhttp頭三層協(xié)議的解析宅广,能看到實(shí)際上頭三層主要處理的是Http協(xié)議中狀態(tài)碼所對(duì)應(yīng)的行為。
Http響應(yīng)狀態(tài)碼大致上可以分為如下幾種情況:
2XX 代表請(qǐng)求成功
200些举,203跟狱,204 代表請(qǐng)求成功,可以對(duì)響應(yīng)數(shù)據(jù)進(jìn)行緩存
30X 代表資源發(fā)生變動(dòng)或者沒有變動(dòng)
300 是指有多種選擇户魏。請(qǐng)求的資源包含多個(gè)位置驶臊,此時(shí)請(qǐng)求也可以看作成功,此時(shí)也會(huì)進(jìn)行緩存起來叼丑。此時(shí)也會(huì)記錄下需要跳轉(zhuǎn)Header中的Location关翎,并重新設(shè)置為全新的跳轉(zhuǎn)url。記住這個(gè)過程是先執(zhí)行了緩存攔截器后鸠信,再執(zhí)行跳轉(zhuǎn)攔截器纵寝。
301 請(qǐng)求的資源已經(jīng)永久移動(dòng)了 會(huì)自動(dòng)重定向。此時(shí)還是一樣會(huì)緩存當(dāng)前的結(jié)果后星立,嘗試獲取Location的url 進(jìn)行重定向(Http 1.0內(nèi)容)爽茴,不允許重定向時(shí)候改變請(qǐng)求方式(如get轉(zhuǎn)化成post)
302 代表臨時(shí)移動(dòng)的資源,所以沒有特殊處理并不會(huì)緩存結(jié)果绰垂,因?yàn)檫@個(gè)響應(yīng)數(shù)據(jù)很可能時(shí)效性很短室奏;但是如果設(shè)置了
Cache-Control
,Expires
這些緩存時(shí)效頭部就會(huì)進(jìn)行緩存劲装,接著會(huì)獲取Location的url 進(jìn)行重定向(Http 1.0內(nèi)容)胧沫,不允許重定向時(shí)候改變請(qǐng)求方式(如get轉(zhuǎn)化成post)303 代表查看其他資源,而這個(gè)過程可以不視作一個(gè)正常的響應(yīng)結(jié)果酱畅,也因?yàn)樵试S改變請(qǐng)求方式琳袄;因此也不會(huì)進(jìn)行緩存,接著會(huì)獲取Location的url 進(jìn)行重定向.
304 代表資源沒有發(fā)生變動(dòng)纺酸,且緩存策略是允許刷新的窖逗。那么就說明服務(wù)器這段時(shí)間內(nèi)對(duì)這個(gè)請(qǐng)求的應(yīng)答沒有變化,客戶端直接從緩存獲取即可餐蔬。此時(shí)客戶端就會(huì)從緩存攔截器中的緩存對(duì)象獲取緩存好的響應(yīng)信息碎紊。
307 同302 也是一個(gè)臨時(shí)移動(dòng)資源的標(biāo)志位佑附,不同的是這是來自Http 1.1協(xié)議。為什么出現(xiàn)一個(gè)一樣的呢仗考?因?yàn)?02在很多瀏覽器的實(shí)現(xiàn)是允許改變請(qǐng)求方式音同,因此307強(qiáng)制規(guī)定不允許改變
308 同301 是一個(gè)永久移動(dòng)的資源路徑,來自Http 1.1.原因也是因?yàn)閺?qiáng)制規(guī)范不允許改變請(qǐng)求方式秃嗜,但是允許進(jìn)行緩存权均。
4XX 客戶端異常或者客戶端需要特殊處理
401 請(qǐng)求要求用戶的身份認(rèn)證锅锨。這個(gè)過程就會(huì)獲取設(shè)置在Authenticator 中的賬號(hào)密碼叽赊,添加到頭部中重試這個(gè)請(qǐng)求。
403 代表拒絕訪問必搞,okhttp不會(huì)做任何處理直接返回
404 代表客戶端請(qǐng)求異常必指,說明這個(gè)url的請(qǐng)求狀態(tài)有問題,okhttp也會(huì)進(jìn)行緩存學(xué)習(xí)恕洲,下一次再一次訪問的時(shí)候就會(huì)直接返回異常塔橡。
405 代表當(dāng)前請(qǐng)求的方式出錯(cuò)了,這個(gè)請(qǐng)求不支持這種請(qǐng)求方式
407 和401類似 不過在這里面代表的是使用代理的Authenticator. authenticate 進(jìn)行賬號(hào)密碼的校驗(yàn)
408 服務(wù)器等待客戶端發(fā)送請(qǐng)求超時(shí)處理 狀態(tài)碼408不常見霜第,但是在HAProxy中會(huì)比較常見葛家。這種情況說明我們可以重復(fù)的進(jìn)行沒有修改過的請(qǐng)求(甚至是非冪等請(qǐng)求),從頭部中獲取對(duì)應(yīng)的key,從而決定是否立即重試
410 代表資源已經(jīng)不可用了庶诡,此時(shí)okhttp也會(huì)學(xué)習(xí)惦银,緩存這個(gè)結(jié)果直到超過緩存時(shí)效咆课。
414 代表請(qǐng)求的URL長(zhǎng)度超出了服務(wù)器可以處理的長(zhǎng)度末誓。很少見這種情況,這種也是數(shù)據(jù)一種異常书蚪,所以okhttp也會(huì)獲取摘要學(xué)習(xí)
421 代表客戶端所在的ip地址到服務(wù)器的連接數(shù)超過了服務(wù)器最大的連接數(shù)喇澡。此時(shí)還是有機(jī)會(huì)進(jìn)行重新請(qǐng)求,因?yàn)樵贖ttp 2.0協(xié)議中允許流的復(fù)用殊校。
5XX 服務(wù)端異常
500 服務(wù)端出現(xiàn)了無法處理的錯(cuò)誤晴玖,直接報(bào)錯(cuò)了。 這種情況不會(huì)做處理为流,直接拋出錯(cuò)誤即可
501 服務(wù)端此時(shí)不支持請(qǐng)求所需要的功能呕屎,服務(wù)器無法識(shí)別請(qǐng)求的方法,并且無法支持對(duì)任何資源的請(qǐng)求敬察。 這種錯(cuò)誤okhhtp可以緩存學(xué)習(xí)秀睛,因?yàn)槭欠?wù)器的web系統(tǒng)需要升級(jí)了。
503 服務(wù)器過載莲祸,暫時(shí)不處理蹂安。一般會(huì)帶上
Retry-After
告訴客戶端延時(shí)多少時(shí)間之后再次請(qǐng)求椭迎。然而okhttp不會(huì)做延時(shí)處理,而是交給開發(fā)者處理田盈,他只會(huì)處理Retry-After
為0的情況畜号,也就是立即處理504 一般是指網(wǎng)關(guān)超時(shí),注意如果okhttp禁止了網(wǎng)絡(luò)請(qǐng)求和緩存也會(huì)返回504
retryAndFollowUpInterceptor
主要處理了如下幾個(gè)方向的問題:
- 1.異常允瞧,或者協(xié)議重試(408客戶端超時(shí)简软,權(quán)限問題,503服務(wù)暫時(shí)不處理述暂,retry-after為0)
- 2.重定向
- 3.重試的次數(shù)不能超過20次替饿。
BridgeInterceptor
主要是把Cookie,Content-type設(shè)置到頭部中贸典。很多時(shí)候视卢,初學(xué)者會(huì)疑惑為什么自己加的頭部會(huì)失效,就是因?yàn)樵贏pplication攔截器中處理后廊驼,又被BridgeInterceptor 覆蓋了据过。需要使用networkInterceptor
CacheInterceptor
主要是處理304等響應(yīng)體的緩存。通過DiskLruCache緩存起來妒挎。
到這里前三層屬于對(duì)Http協(xié)議處理的攔截器就完成了绳锅,接下來幾層就是okhttp如何管理鏈接的。