参考文档
简书上 Okhttp的介绍https://zhuanlan.zhihu.com/p/116777864
所有的代码注释已经提交到https://gitee.com/you-zijun/okhttp/commit/db6d553705220faffbfb3e765672bb89254316bb
概述
RetryAndFollowUpInterceptor是OkHttp的中定义的默认拦截器中的第一个,主要负责进行重试和重定向的处理。
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)
关于他的处理流程,我觉得下图描述的非常准确
下面开始贴代码,走读流程
代码流程走读
进去先看到一个while循环
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) {
//这里主要是一个while循环,在循环里处理异常和重定向请求。
//每一次捕获异常或收到重定向之后,就重新配置下request,然后continue
//如果请求成功,则return response
}
然后首先是对异常的处理
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
}
从上面代码中可以看出,会首先进行网络请求,如果报出异常,则通过recover方法根据异常类型和原始的request进行尝试恢复,如果能够恢复,则continue用修复之后的request重新进行process操作。
如果不能恢复,则直接抛出异常。
我们看下recover的逻辑
private fun recover(
e: IOException,
call: RealCall,
userRequest: Request,
requestSendStarted: Boolean
): Boolean {
// The application layer has forbidden retries.
// 是否手动禁止了重试
if (!client.retryOnConnectionFailure) return false
// 检测request是否支持重新发送
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false
// 通过异常类型来判断是否可以恢复
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
}
前面主要根据用户的设置,request当前的状态跑断是否可以重试,主要的逻辑在call.retryAfterFailure
这里补充一点背景,每一个RealCall都配置一个Exchange和ExchangeFinder。每一个Exchange负责处理一个单独的http请求和回复对,并管理ExchangeCodec上的链接,而ExchangeCodec是用来处理真实的http请求的
我们看下retryAfterFailure
/**
* Returns true if the current route has a failure that retrying could fix, and that there's
* a route to retry on.
*
* 如果当前路由可以恢复, 或者还有新的路由, 则返回true, 否则返回false
*/
fun retryAfterFailure(): Boolean {
if (refusedStreamCount == 0 && connectionShutdownCount == 0 && otherFailureCount == 0) {
//如果当前连接没有失败, 则返回false
return false // Nothing to recover from.
}
if (nextRouteToTry != null) {
//如果有新的路由, 则返回true
return true
}
val retryRoute = retryRoute()
if (retryRoute != null) {
// Lock in the route because retryRoute() is racy and we don't want to call it twice.
// 看当前连接的路由是否可以重试, 如果失败次数过多等, 则不重试了
nextRouteToTry = retryRoute
return true
}
// If we have a routes left, use 'em.
if (routeSelection?.hasNext() == true) return true
// If we haven't initialized the route selector yet, assume it'll have at least one route.
val localRouteSelector = routeSelector ?: return true
// If we do have a route selector, use its routes.
return localRouteSelector.hasNext()
}
上述代码基本上就是重新找一个Route。
接下来看重定向和逻辑
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
//我把priorResponse理解成重定向之前的那次response. 如果有, 说明有进行过重定向操作, 将该response返回出去
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build()
}
val exchange = call.interceptorScopedExchange
// 检查是否需要重定向
val followUp = followUpRequest(response, exchange)
if (followUp == null) {
// 如果不需要重定向, 就直接返回response了.
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")
}
// 对于需要followUp的, 进行followUp
request = followUp
priorResponse = response
从上面代码中可以看到, 如果不需要进行重定向,就直接返回了response,如果需要,则重新配置request, 进行重新的请求。
关键的逻辑在followUpRequest
@Throws(IOException::class)
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) {
//http模式下是不能接受407的response code的
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)
//重定向
// 根据response来构造一个重定向的request
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
}
值得注意的是,这里我们将followUp定义为“重定向”即使是不准确的,因为他还处理超时等相关逻辑
其他
有一个新的认知,之前我以为interceptor对象是复用的,但是看到代码之后菜之后,每一个call都会新建一堆interceptor对象