重试和重定向拦截器
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val realChain = chain as RealInterceptorChain
val transmitter = realChain.transmitter()
var followUpCount = 0
var priorResponse: Response? = null
// 重试和重定向
while (true) {
transmitter.prepareToConnect(request)
if (transmitter.isCanceled) {
throw IOException("Canceled")
}
var response: Response
var success = false
try {
response = realChain.proceed(request, transmitter, null)
success = true
} catch (e: RouteException) {
// 路由失败,请求还没有发出
if (!recover(e.lastConnectException, transmitter, false, request)) {
throw e.firstConnectException
}
continue
} catch (e: IOException) {
// 服务器失败,请求已经发出
val requestSendStarted = e !is ConnectionShutdownException
// 报告并尝试从与服务器通信失败中恢复。如果“e”是可恢复的,则返回true;如果失败是永久性的,则返回false。
// 只有在缓冲了正文或在发送请求之前发生故障时,才可以恢复具有正文的请求。
if (!recover(e, transmitter, requestSendStarted, request)) throw e
continue
} finally {
// 网络抛出异常,释放资源,exchange赋值为null
if (!success) {
transmitter.exchangeDoneDueToException()
}
}
// 附加先前响应如果存在,这样的反应从来没有实体。
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build()
}
val exchange = response.exchange
val route = exchange?.connection()?.route()
val followUp = followUpRequest(response, route)
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
transmitter.timeoutEarlyExit()
}
return response
}
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
return response
}
// 清空响应体
response.body?.closeQuietly()
if (transmitter.hasExchange()) {
// 撤消此Exchange对流的访问
exchange?.detachWithViolence()
}
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
request = followUp
priorResponse = response
}
}
}
fun prepareToConnect(request: Request) {
if (this.request != null) {
// 判断路由是否可用
if (this.request!!.url.canReuseConnectionFor(request.url) && exchangeFinder!!.hasRouteToTry()) {
return
}
check(exchange == null)
if (exchangeFinder != null) {
// 强制释放链接
maybeReleaseConnection(null, true)
exchangeFinder = null
}
}
// 初始化交换查找器
this.request = request
this.exchangeFinder = ExchangeFinder(
this, connectionPool, createAddress(request.url), call, eventListener)
}
private fun followUpRequest(userResponse: Response, route: Route?): Request? {
val responseCode = userResponse.code
val method = userResponse.request.method
when (responseCode) {
// 代理认证,默认返回null
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)
}
// 认证,默认返回null
HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse)
// 重定向
HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT -> {
if (method != "GET" && method != "HEAD") {
return null
}
return buildRedirectRequest(userResponse, method)
}
// 重定向
HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
return buildRedirectRequest(userResponse, method)
}
// 超时
HTTP_CLIENT_TIMEOUT -> {
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
}
// HTTP不可用
HTTP_UNAVAILABLE -> {
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
}
else -> return null
}
}
重试和重定向总结
| HTTP响应码 |
followUpRequest |
| HTTP_PROXY_AUTH(407) |
添加代理认证,默认放弃 |
| HTTP_UNAUTHORIZED(401) |
添加认证,默认放弃 |
| HTTP_PERM_REDIRECT(308)HTTP_TEMP_REDIRECT(307) |
重定向 |
| HTTP_MULT_CHOICE(300)HTTP_MOVED_PERM(301)HTTP_MOVED_TEMP(302)HTTP_SEE_OTHER(303) |
重定向 |
| HTTP_CLIENT_TIMEOUT(408) |
默认放弃,或延迟0秒重试 |
| HTTP_UNAVAILABLE(503) |
延迟0秒重试 |
| 其他 |
成功或放弃 |