OkHttp的請求過程
上篇文章說到 OkHttp 的請求過程是在getResponseWithInterceptorChain()
裏,下面分析下請求和響應過程,先看下這個方法實現:
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)
}
}
}
這個核心方法一共分爲三部分,組成了 OkHttp 的核心代碼。
getResponseWithInterceptorChain
分析
添加攔截器
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)
上面代碼很簡單,就是拼裝所有攔截器,第一個攔截器client.interceptors
是開發者自定義的攔截器。
組裝
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
在這一步,把配置信息和攔截器組裝成一個RealInterceptorChain
。
處理
下面這行代碼,是真正處理的地方:
val response = chain.proceed(originalRequest)
繼續點進去,看方法實現:
override fun proceed(request: Request): Response {
check(index < interceptors.size)
calls++
if (exchange != null) {
check(exchange.finder.sameHostAndPort(request.url)) {
"network interceptor ${interceptors[index - 1]} must retain the same host and port"
}
check(calls == 1) {
"network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
}
}
// 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")
if (exchange != null) {
check(index + 1 >= interceptors.size || next.calls == 1) {
"network interceptor $interceptor must call proceed() exactly once"
}
}
check(response.body != null) { "interceptor $interceptor returned a response with no body" }
return response
}
RealInterceptorChain
的proceed
方法主要做兩件事情,取出對應index
的攔截器,index+1
組成另一個RealInterceptorChain
,把新組成的RealInterceptorChain
傳給攔截器執行攔截代碼。
val interceptor = interceptors[index]
這裏是傳入的index
是 0 ,所以當前RealInterceptorChain
對應的proceed
方法中的攔截器第一個攔截器,val next = copy(index = index + 1, request = request)
是一個配置信息一樣但是index
是 1 的新RealInterceptorChain
。注意這裏不是攔截器,是一個chain
。
val response = interceptor.intercept(next)
這裏是當前的攔截器開始工作,這是一個接口,它的實現裏有一句代碼是上面見過的,隨便點進一個攔截器查看,這裏點的是RetryAndFollowUpInterceptor
:
response = realChain.proceed(request)
而這個realChain
就是上面interceptor.intercept(next)
傳入的next
,根據index
取出的是第二個攔截器。是不是恍然大悟,這是俄羅斯套娃,一個接一個,並不是一個方法到底的。在第一個攔截器的攔截實現方法裏,代碼執行到中間,會去執行第二個攔截器,然後去執行第三個、第四個。等返回後再一個個返回。
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) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
throw e.firstConnectException.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e.firstConnectException
}
newExchangeFinder = false
continue
} catch (e: IOException) {
// An attempt to communicate with a server failed. The request may have been sent.
if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
throw e.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e
}
newExchangeFinder = false
continue
}
// Attach the prior response if it exists. Such responses never have a body.
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)
}
}
}
這裏也分爲三部分:
- 配置信息,執行自己的攔截操作。
- 調用下一個攔截器執行
- 下一個攔截器返回後的操作
整個流程如上圖,像一個流水線一樣,從左向右執行,然後在返回執行。
攔截器分析
RetryAndFollowUpInterceptor
請求失敗重試和跟從重定向攔截器。
while (true) {}
循環裏的第一行代碼:
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
這是爲接下來的連接做準備,爲當前call
創建一個能尋找可用連接的對象ExchangeFinder
this.exchangeFinder = ExchangeFinder(
connectionPool,
createAddress(request.url),
this,
eventListener
)
這個循環裏面如果出現 RouteException 連接異常,或者 IOException 異常,如果可以重試,就會continue
重試。能否重試的判斷,在recover
裏:
if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
throw e.firstConnectException.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e.firstConnectException
}
下面是判斷是否要執行重定向:
val exchange = call.interceptorScopedExchange
val followUp = followUpRequest(response, exchange)
會根據返回的狀態碼判斷是否是重定向,並執行重定向:
HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
return buildRedirectRequest(userResponse, method)
}
直到沒有重試和重定向,return response
退出循環。
分析完這個攔截器,總結下它的作用:
- 準備連接
- 調用下一個攔截器
- 重試和重定向
BridgeInterceptor
橋接攔截器,主要功能是爲當前請求添加header
。默認添加requestBuilder.header("Accept-Encoding", "gzip")
壓縮,同時響應數據自動解壓。
分析完這個攔截器,總結下它的作用:
- 添加
header
- 調用下一個攔截器
- 解壓
CacheInterceptor
緩存攔截器。如果有緩存直接返回:
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)
}
}
這裏利用緩存構建一個Response
。
如果沒有去執行下一個攔截器做網絡請求,這行代碼已經出現很多次了:
networkResponse = chain.proceed(networkRequest)
然後緩存結果:
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 strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
ConnectInterceptor
最核心的攔截器,請求的建立就是這裏發生的。
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val exchange = realChain.call.initExchange(chain)
val connectedChain = realChain.copy(exchange = exchange)
return connectedChain.proceed(realChain.request)
}
}
這行代碼和上面的不太一樣:
return connectedChain.proceed(realChain.request)
這裏直接return
,而其他的攔截器都是再做一些後置工作,比如緩存攔截器會解壓縮。這是因爲這裏是真正建立連接的地方,至此請求建立完成,並沒有需要的後續操作。
下面分析這個攔截器的攔截操作:
/** Finds a new or pooled connection to carry a forthcoming request and response. */
internal fun initExchange(chain: RealInterceptorChain): Exchange {
synchronized(this) {
check(expectMoreExchanges) { "released" }
check(!responseBodyOpen)
check(!requestBodyOpen)
}
val exchangeFinder = this.exchangeFinder!!
val codec = exchangeFinder.find(client, chain)
val result = Exchange(this, eventListener, exchangeFinder, codec)
this.interceptorScopedExchange = result
this.exchange = result
synchronized(this) {
this.requestBodyOpen = true
this.responseBodyOpen = true
}
if (canceled) throw IOException("Canceled")
return result
}
`val codec = exchangeFinder.find(client, chain)` 這一行找到可以連接:
private fun findHealthyConnection(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean,
doExtensiveHealthChecks: Boolean
): RealConnection {
while (true) {
val candidate = findConnection(
connectTimeout = connectTimeout,
readTimeout = readTimeout,
writeTimeout = writeTimeout,
pingIntervalMillis = pingIntervalMillis,
connectionRetryEnabled = connectionRetryEnabled
)
// Confirm that the connection is good.
if (candidate.isHealthy(doExtensiveHealthChecks)) {
return candidate
}
// If it isn't, take it out of the pool.
candidate.noNewExchanges()
// Make sure we have some routes left to try. One example where we may exhaust all the routes
// would happen if we made a new connection and it immediately is detected as unhealthy.
if (nextRouteToTry != null) continue
val routesLeft = routeSelection?.hasNext() ?: true
if (routesLeft) continue
val routesSelectionLeft = routeSelector?.hasNext() ?: true
if (routesSelectionLeft) continue
throw IOException("exhausted all routes")
}
}
在循環裏通過findConnection
拿到可用連接,驗證是否健康(連接正常,心跳正常等),健康就返回,一共有五種方式,分爲初始狀態獲取和再次進入獲取。
初始狀態三次獲取連接方式
-
初始狀態去連接池尋找連接:
if (connectionPool.callAcquirePooledConnection(address, call, null, false)) { val result = call.connection!! eventListener.connectionAcquired(call, result) return result }
這裏會判斷連接數量是否超限。
-
再次去連接池尋找鏈接,參數不同,傳入了路由:
if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) { val result = call.connection!! eventListener.connectionAcquired(call, result) return result }
獲取路由配置,所謂路由其實就是代理、ip地址等參數的一個組合。拿到路由後嘗試重新從連接池中獲取連接,這裏主要針對http2的多路複用。
- 沒有可用連接,新建連接:
newConnection.connect(
connectTimeout,
readTimeout,
writeTimeout,
pingIntervalMillis,
connectionRetryEnabled,
call,
eventListener
)
。。。
connectionPool.put(newConnection)
有了連接並沒有結束,而是再去連接池取一次,目的是如果同時2個請求建立連接,符合多路複用,就會丟掉一個,節省資源,最後一個參數是說只拿多路複用的連接:
if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
val result = call.connection!!
nextRouteToTry = route
newConnection.socket().closeQuietly()
eventListener.connectionAcquired(call, result)
return result
}
然後把連接放入連接池:
connectionPool.put(newConnection)
總結:
- 獲取無多路複用的連接
- 獲取所有連接
- 創建連接,只獲取多路複用的連接
再次進入獲取連接
上面是初次獲取的時候,如果重定向等第二次獲取連接的時候,也有兩種方式。
- 連接不可用,關閉連接進入初始狀態,但是複用路由信息,不需要再次尋找路由:
if (callConnection != null) {
var toClose: Socket? = null
synchronized(callConnection) {
if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
toClose = call.releaseConnectionNoEvents()
}
}
如果這個連接不接受新的連接,不能複用,或者 Http 重定向到 Https,就會釋放掉這個連接。
- 如果這個連接可用,會直接重用這個連接:
if (call.connection != null) {
check(toClose == null)
return callConnection
}
建立連接
點進newConnection.connect
,查看如何建立連接。
while (true) {
try {
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener)
if (rawSocket == null) {
// We were unable to connect the tunnel but properly closed down our resources.
break
}
} else {
connectSocket(connectTimeout, readTimeout, call, eventListener)
}
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)
eventListener.connectEnd(call, route.socketAddress, route.proxy, protocol)
break
} catch (e: IOException) {
socket?.closeQuietly()
rawSocket?.closeQuietly()
socket = null
rawSocket = null
source = null
sink = null
handshake = null
protocol = null
http2Connection = null
allocationLimit = 1
eventListener.connectFailed(call, route.socketAddress, route.proxy, null, e)
if (routeException == null) {
routeException = RouteException(e)
} else {
routeException.addConnectException(e)
}
if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException
}
}
}
route.requiresTunnel()
,如果代理是 Http ,目標是 Https ,那麼創建socket
之後會創建一個createTunnelRequest
:
connectSocket(connectTimeout, readTimeout, call, eventListener)
tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url)
?: break // T
如果是正常的 Http 請求,那麼久創建一個socket
:
connectSocket(connectTimeout, readTimeout, call, eventListener)
其實現如下:
val rawSocket = when (proxy.type()) {
Proxy.Type.DIRECT, Proxy.Type.HTTP -> address.socketFactory.createSocket()!!
else -> Socket(proxy)
}
socket
建立之後,就去創建 Http 連接:
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)
這裏會根據支持創建不同版本的 Http 連接和加密連接。
CallServerInterceptor
這個攔截是向服務器發送數據並接受數據的攔截器,代碼比較簡單。