源码
RetryAndFollowUpInterceptor拦截器是用来实现重试和重定向的
所有的拦截器都会去实现intercept方法
@Throws(IOException::class)
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)
}
}
}
第一步 从拦截连中取出request放入一个死循环中,既然是死循环,那么跳出循环的条件也就是最重要的
第一个if很简单,如果请求被取消则抛出异常跳出循环
第二个和第三个if都是调用recover方法,上面注释是 “通过路由连接的尝试失败。请求将不会被发送” 。
/**
* Report and attempt to recover from a failure to communicate with a server. Returns true if
* `e` is recoverable, or false if the failure is permanent. Requests with a body can only
* be recovered if the body is buffered or if the failure occurred before the request has been
* sent.
* 报告并尝试从与服务器通信失败中恢复。返回true,如果' e '是可恢复的,如果失败是永久性的,则为false。
* 只允许带有正文的请求如果请求体已经缓冲,或者失败发生在请求之前,则恢复发送。
*/
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
}
看到recover方法,五个if,如果触发了任何一个则返回false,则会抛出异常跳出循环。 也就是如果没有禁止重试或者出致命的bug导致不能重试,就会不停的重试请求。
刚才讲完了RetryAndFollowUpInterceptor重试机制的实现,现在再来看重定向机制是如何实现的。
来看到followUpRequest方法的注释:
找出HTTP请求响应接收[userResponse]。这将添加认证头,遵循重定向或处理客户端请求超时。如果一个follow-up要么是不必要的,要么是不适用的,返回null。这个方法就是来处理重定向和超时的。
关于请求重定向这块的源码,必须要对响应码的有一定的了解,所以我们先简单回顾一下响应码的区间和类型
- 100-199:请求处理中
- 200-299:请求被接受(例如:200 请求成功)
- 300-399:请求被重定向(例如:307 地址重定向)
- 400-499:客户端请求参数错误(例如:403 请求被拒绝,404 请求地址不存在)
- 500-599:服务器解析请求发生错误(例如:500 服务器出错,503 接口出错)
当状态码为300-399,则调用buildRedirectRequest方法 当状态码为其他时,抛出对应的异常
private fun buildRedirectRequest(userResponse: Response, method: String): Request? {
// Does the client allow redirects?
// 客户端允许重定向吗?
if (!client.followRedirects) return null
val location = userResponse.header("Location") ?: return null
// Don't follow redirects to unsupported protocols.
// 不要遵循重定向到不支持的协议。
val url = userResponse.request.url.resolve(location) ?: return null
// If configured, don't follow redirects between SSL and non-SSL.
// 如果配置了,不要在SSL和非SSL之间进行重定向。
val sameScheme = url.scheme == userResponse.request.url.scheme
if (!sameScheme && !client.followSslRedirects) return null
// Most redirects don't include a request body.
// 大多数重定向不包括请求体。
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")
}
}
// When redirecting across hosts, drop all authentication headers. This
// is potentially annoying to the application layer since they have no
// way to retain them.
// 当跨主机重定向时,删除所有身份验证头。这可能会让应用层感到烦恼,因为它们没有保留它们的方法。
if (!userResponse.request.url.canReuseConnectionFor(url)) {
requestBuilder.removeHeader("Authorization")
}
return requestBuilder.url(url).build()
}
第一个判断,禁止重定向则返回null
第二个判断,没有重定向的地址(Location)则返回null
第三个判断,配置了不要再SSL和非SSL直接重定向则返回空
然后再对请求头进行封装,返回请求