开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第37天,点击查看活动详情
文章中源码的OkHttp版本为4.10.0
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
OkHttp源码分析(一)中主要分析了使用、如何创建,再到发起请求,这篇文章主要来分析OkHttp的拦截器链。
1.拦截器链
- 前面的代码在发起请求的时候代码走到了
getResponseWithInterceptorChain()
,这个方法得到了response
的响应,他做了什么呢,先看源码
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
//构建了一个完整的拦截器栈
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 transmitter.noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
getResponseWithInterceptorChain
方法是整个OkHttp实现责任链模式的核心,在这个方法中除了众多拦截器是重点之外还有chain.proceed
这个重点,这里利用了责任链模式,先看一下这里面做了什么事情:
override fun proceed(request: Request): Response {
check(index < interceptors.size)
//统计当前拦截器调用proceed方法的次数
calls++
//这里是对拦截器调用proceed方法的限制,
//在ConnectInterceptor及其之后的拦截器最多只能调用一次proceed!!
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"
}
}
//创建并调用链中的下一个拦截器
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) {
//保证在ConnectInterceptor及其之后的拦截器至少调用一次proceed!!
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
}
上面的proceed
函数中的逻辑就做了一件事,创建下一级责任链并调用下一个拦截器,在getResponseWithInterceptorChain
函数中除CallServerInterceptor
拦截器之外它前面的每一个拦截器都会调用一次proceed
并且只会调用一次,call++
的目的是为了保证责任链能依次调用。
下面再来聊一聊拦截器,如果没有自定义的拦截器那么就只有5个拦截器,这5个拦截器的功能如下图
1.RetryAndFollowUpInterceptor:
- 重定向或者重试拦截器,同时还创建了一个连接管理池
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
companion object {
//定义重试或者重定向的次数
//Chrome遵循21个重定向;Firefox, curl和wget跟随20;Safari是16;HTTP/1.0推荐5。
private const val MAX_FOLLOW_UPS = 20
}
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//chain转换为具体的拦截器链,RealInterceptorChain实现了Interceptor.Chain
val realChain = chain as RealInterceptorChain
var request = chain.request
//这个call就是RealCall对象
val call = realChain.call
var followUpCount = 0
var priorResponse: Response? = null
//不是重试并且可以执行新的路由
var newExchangeFinder = true
var recoveredFailures = listOf<IOException>()
while (true) {
//newExchangeFinder定义为true因此通过enterNetworkInterceptorExchange函数中
//创建了一个新的ExchangeFinder对象,用来管理连接池
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
}
//后面的处理都结束之后会回传response
//如果存在,请附上先前的response。这样的response是没有主体的。
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build()
}
val exchange = call.interceptorScopedExchange
//找出要响应接收response的HTTP请求。
//这将添加身份验证头,遵循重定向或处理客户端请求超时。
//如果后续操作是不必要的或不适用的,则返回null。
val followUp = followUpRequest(response, exchange)
//获取到的request为空则直接返回response
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
call.timeoutEarlyExit()
}
closeActiveExchange = false
return response
}
val followUpBody = followUp.body
//request有值且只允许被调用一次则返回response
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若不为空则重新赋值后继续发起请求
request = followUp
priorResponse = response
} finally {
//如果closeActiveExchange为true则当前交换不被使用且关闭
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
fun enterNetworkInterceptorExchange(request: Request, newExchangeFinder: Boolean) {
check(interceptorScopedExchange == null)
synchronized(this) {
//判断之前的请求是否是打开状态,如果是则不能发出新的请求
check(!responseBodyOpen) {
"cannot make a new request because the previous response is still open: " +
"please call response.close()"
}
check(!requestBodyOpen)
}
if (newExchangeFinder) {
//创建了ExChangeFinder
this.exchangeFinder = ExchangeFinder(
connectionPool,
createAddress(request.url),
this,
eventListener
)
}
}
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 -> {
//407异常处理
}
HTTP_UNAUTHORIZED -> {
//401异常处理
}
HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
//300 异常处理
//构建重定向请求
return buildRedirectRequest(userResponse, method)
}
HTTP_CLIENT_TIMEOUT -> {
//408异常处理
}
HTTP_UNAVAILABLE -> {
//503异常处理
}
HTTP_MISDIRECTED_REQUEST -> {
//OkHttp可以合并HTTP/2连接,即使域名不同。看到
//RealConnection.isEligible()。如果我们尝试这样做,并且服务器返回HTTP 421,
//那么可以尝试另一个连接。
}
}
}
/**
* 构建重定向请求
*/
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
// 如果配置了,不要遵循SSL和非SSL之间的重定向
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 == StatusLine.HTTP_PERM_REDIRECT ||
responseCode == StatusLine.HTTP_TEMP_REDIRECT
if (HttpMethod.redirectsToGet(method) && responseCode != StatusLine.HTTP_PERM_REDIRECT && responseCode != StatusLine.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()
}
2.BridgeInterceptor:
- 桥接拦截器,负责把用户请求转换成网络请求,把网络响应转换成对用户友好的响应。
- 源码方面没什么难度就是添加了一些请求头,需要注意的是Accept-Encoding请求头,如果用户自己添加了那就需要自己解压,如果用户没有添加则请求过程会自己添加gizp,同时会自动解压。
3.CacheInterceptor:
- CacheInterceptor就是负责更新和读取缓存的,内部是通过okio进行读取和写入的
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
@Throws(IOException::class)
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) {
// 候选缓存不适用,关闭
cacheCandidate.body?.closeQuietly()
}
// 如果网络被禁止使用则缓存为空并且抛出异常504
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 (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build().also {
listener.cacheHit(call, it)
}
}
var networkResponse: Response? = null
try {
//让下一个拦截器处理
networkResponse = chain.proceed(networkRequest)
} finally {
//防止内存泄露
if (networkResponse == null && cacheCandidate != null) {
cacheCandidate.body?.closeQuietly()
}
}
//有缓存
if (cacheResponse != null) {
//服务器返回状态码为304则返回缓存结果
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()
//在合并头之后更新缓存
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)) {
//将请求结果进行缓存
val cacheRequest = cache.put(response)
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
//只记录条件缓存丢失的日志
listener.cacheMiss(call)
}
}
}
//缓存无效时清除
if (HttpMethod.invalidatesCache(networkRequest.method)) {
try {
cache.remove(networkRequest)
} catch (_: IOException) {
// 无法写入缓存
}
}
}
//返回网络读取的结果
return response
}
}
4.ConnectInterceptor:
- 连接拦截器就是用来建立连接的
//建立连接并继续到下一个拦截程序,网络可以用于返回的响应,也可以使用GET验证缓存的响应。
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
//返回一个携带新请求的exchange对象
//exchange主要作用是发送请求和接收响应的。
val exchange = realChain.call.initExchange(chain)
val connectedChain = realChain.copy(exchange = exchange)
//让下一层拦截器处理,同时将exchange一起传递过去
return connectedChain.proceed(realChain.request)
}
}
- 连接又是如何建立连接的
//查找新连接或池连接以承载即将到来的请求和响应
internal fun initExchange(chain: RealInterceptorChain): Exchange {
synchronized(this) {
check(expectMoreExchanges) { "released" }
check(!responseBodyOpen)
check(!requestBodyOpen)
}
val exchangeFinder = this.exchangeFinder!!
//exchangeFinder是在RetryAndFollowUpInterceptor中创建的
//找到可用的连接后创建一个新的解码器并返回
val codec = exchangeFinder.find(client, chain)
//创建Exchange对象,主要负责发送请求和接收响应的
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
}
- exchangeFinder.find(client, chain)
fun find(
client: OkHttpClient,
chain: RealInterceptorChain
): ExchangeCodec {
try {
//这里就是查找可用的链接,如果不可用则一直重复查找,知道找到为止
val resultConnection = findHealthyConnection(
connectTimeout = chain.connectTimeoutMillis,
readTimeout = chain.readTimeoutMillis,
writeTimeout = chain.writeTimeoutMillis,
pingIntervalMillis = client.pingIntervalMillis,
connectionRetryEnabled = client.retryOnConnectionFailure,
doExtensiveHealthChecks = chain.request.method != "GET"
)
//创建并返回一个新的编码器
return resultConnection.newCodec(client, chain)
} catch (e: RouteException) {
trackFailure(e.lastConnectException)
throw e
} catch (e: IOException) {
trackFailure(e)
throw RouteException(e)
}
}
- exchangeFinder.findHealthyConnection
@Throws(IOException::class)
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
)
//确认找到的连接是否可以正常使用
if (candidate.isHealthy(doExtensiveHealthChecks)) {
return candidate
}
//如果连接不可用就从连接池中取出
candidate.noNewExchanges()
//这里就是确保连接可用,如果前面找到的连接不可用那么继续查找
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")
}
}
- exchangeFinder.findConnection
@Throws(IOException::class)
private fun findConnection(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean
): RealConnection {
//尝试重用调用中的连接。
val callConnection = call.connection
if (callConnection != null) {
var toClose: Socket? = null
synchronized(callConnection) {
if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
toClose = call.releaseConnectionNoEvents()
}
}
//如果连接还没有被释放则重用
if (call.connection != null) {
check(toClose == null)
return callConnection
}
//连接被释放
toClose?.closeQuietly()
//从连接的分配列表中删除
eventListener.connectionReleased(call, callConnection)
}
//需要一个新的连接。给它新的数据。
refusedStreamCount = 0
connectionShutdownCount = 0
otherFailureCount = 0
//从连接池获取
if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
val result = call.connection!!
eventListener.connectionAcquired(call, result)
return result
}
//连接池中什么都没有的情况下走这里
val routes: List<Route>?
val route: Route
if (nextRouteToTry != null) {
//使用来自前一个合并连接的路由
routes = null
route = nextRouteToTry!!
nextRouteToTry = null
} else if (routeSelection != null && routeSelection!!.hasNext()) {
// 在现有路由中选择
routes = null
route = routeSelection!!.next()
} else {
//计算出一个新的连接,这是一个阻塞操作
var localRouteSelector = routeSelector
if (localRouteSelector == null) {
localRouteSelector = RouteSelector(address, call.client.routeDatabase, call, eventListener)
this.routeSelector = localRouteSelector
}
val localRouteSelection = localRouteSelector.next()
routeSelection = localRouteSelection
routes = localRouteSelection.routes
if (call.isCanceled()) throw IOException("Canceled")
//这里有了一组新的IP地址,所以再次尝试从连接池中获取
if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
val result = call.connection!!
eventListener.connectionAcquired(call, result)
return result
}
route = localRouteSelection.next()
}
//创建一个新的连接
val newConnection = RealConnection(connectionPool, route)
call.connectionToCancel = newConnection
try {
//这里连接了服务器
newConnection.connect(
connectTimeout,
readTimeout,
writeTimeout,
pingIntervalMillis,
connectionRetryEnabled,
call,
eventListener
)
} finally {
call.connectionToCancel = null
}
call.client.routeDatabase.connected(newConnection.route())
if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
val result = call.connection!!
nextRouteToTry = route
newConnection.socket().closeQuietly()
eventListener.connectionAcquired(call, result)
return result
}
synchronized(newConnection) {
//添加到连接池中
connectionPool.put(newConnection)
call.acquireConnectionNoEvents(newConnection)
}
eventListener.connectionAcquired(call, newConnection)
return newConnection
}
- 从
exchangeFinder.find
开始到exchangeFinder.findConnection
只做了一件事,就是先尝试重用连接,如果不能重用就从连接池中取出一个新的连接,如果无法取出一个连接就创建一个新的连接并添加到连接池中。 - 服务器是怎么连接的,看这行代码
newConnection.connect
fun connect(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean,
call: Call,
eventListener: EventListener
) {
...
while (true) {
try {
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener)
if (rawSocket == null) {
//无法连接
break
}
} else {
//在socket上构建完整HTTP或HTTPS连接
connectSocket(connectTimeout, readTimeout, call, eventListener)
}
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)
eventListener.connectEnd(call, route.socketAddress, route.proxy, protocol)
break
} catch (e: IOException) {
...
}
...
}
connect
中最终调用的是connectSocket
方法
@Throws(IOException::class)
private fun connectSocket(
connectTimeout: Int,
readTimeout: Int,
call: Call,
eventListener: EventListener
) {
val proxy = route.proxy
val address = route.address
//创建socket对象
val rawSocket = when (proxy.type()) {
Proxy.Type.DIRECT, Proxy.Type.HTTP -> address.socketFactory.createSocket()!!
else -> Socket(proxy)
}
this.rawSocket = rawSocket
eventListener.connectStart(call, route.socketAddress, proxy)
rawSocket.soTimeout = readTimeout
try {
//进行socket连接
Platform.get().connectSocket(rawSocket, route.socketAddress, connectTimeout)
} catch (e: ConnectException) {
throw ConnectException("Failed to connect to ${route.socketAddress}").apply {
initCause(e)
}
}
try {
//okio的接口,用于输入,类似InputStream
source = rawSocket.source().buffer()
//okio的接口,用于输出,类似OutputStream
sink = rawSocket.sink().buffer()
} catch (npe: NullPointerException) {
if (npe.message == NPE_THROW_WITH_NULL) {
throw IOException(npe)
}
}
}
- 这个方法就是创建了一个
socket
对象然后使用socket
建立连接,利用okio
的输入输出接口获取输入/输出流
5.CallServerInterceptor:
- 请求拦截器,在前置准备工作完成后,真正发起了网络请求。
- 先写入请求头,如果是GET请求则请求完毕,如果是POST请求则等待返回状态码为100时在发送请求体
- 然后读取响应头,经过判断后(非空响应体)再读取响应体
- 拿到最终结果后一层层的向上传递,会经过之前的每一个拦截器最终回到自己定义的回调中
6.拦截器的总结:
- 首先经过
RetryAndFollowUpIntercept
拦截器,它主要负责重试和重定向,并且重试或者重定向的次数不能大于20次,同时它还创建了一个ExchangeFinder
对象用于管理连接池为后续的连接做准备; - 第二个拦截器是
BridgeInterceptor
拦截器,这个拦截器主要是补充了请求头,把用户请求转换成网络请求,网络响应转换成用户可以接收的响应,同时还需要注意的一点是如果用户手动添加了Accept-Encoding
那就需要处理解压操作; - 第三个拦截器是
CacheInterceptor
拦截器,主要作用就是缓存response
并且它的内部是通过okio
来处理缓存的; - 第四个拦截器是
ConnectInterceptor
拦截器,这个拦截器是负责建立连接的,从代码中可知最终是通过RealConnection
对象建立socket
连接的并且获得了输入输出流为下一步读写做准备,RealConnection
对象的获取时优先复用的,如果无法复用则从连接池中获取,如果无法获取则创建一个新的连接并将其放入连接池中; - 第五个拦截器是
CallServerInterceptor
拦截器,它真正发起了网络请求,负责数据的读取和写入。