Github: okhttp 分析版本:930d4d0

This interceptor recovers from failures and follows redirects as necessary

intercept(chain: Interceptor.Chain)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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) {
// 为 request 准备 stream
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) { // 路由异常 RouteException
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.lastConnectException, transmitter, false, request)) { // 检测路由异常是否能重新连接
throw e.firstConnectException
}
continue // 可以重新连接,重新走 while 循环
} catch (e: IOException) { // 检测该IO异常是否能重新连接
// An attempt to communicate with a server failed. The request may have been sent.
val requestSendStarted = e !is ConnectionShutdownException
if (!recover(e, transmitter, requestSendStarted, request)) throw e
continue
} finally {
// The network call threw an exception. Release any resources.
if (!success) {
transmitter.exchangeDoneDueToException() // 释放
}
}

// priorResponse 是用来保存前一个 Resposne 的,当发现需要重定向,则将当前 Resposne 设置给priorResponse,再执行一遍流程,这里可以看到将前一个 Response 和当前的 Resposne 结合在一起了,直到不需要重定向了,则将 priorResponse 和 Resposne 结合起来。
// 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 = response.exchange
val route = exchange?.connection()?.route()
val followUp = followUpRequest(response, route) // 判断是否需要重定向,如果需要重定向则返回一个重定向的 Request,没有则为 null

if (followUp == null) {
// 不需要重定向
if (exchange != null && exchange.isDuplex) {
transmitter.timeoutEarlyExit()
}
// 返回 response
return response
}

// 需要重定向
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
return response
}

// 关闭响应流
response.body()?.closeQuietly()
if (transmitter.hasExchange()) {
exchange?.detachWithViolence()
}

// 重定向次数++,并且小于最大重定向次数MAX_FOLLOW_UPS(20)
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}

request = followUp
priorResponse = response
}
}

// ...
}

RouteExceptionIOException 异常检测都会调用 recover() 方法进行判断重试

recover(e: IOException, transmitter: Transmitter, requestSendStarted: Boolean, userRequest: Request)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
//...

/**
* 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.
*/

private fun recover(
e: IOException,
transmitter: Transmitter,
requestSendStarted: Boolean,
userRequest: Request

): Boolean {

// 判断 OkHttpClient 是否支持失败重连的机制
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false

// 与之前版本的 UnrepeatableRequestBody 类似,只能请求一次的请求
// 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 (!transmitter.canRetry()) return false
// For failure recovery, use the same route selector with a new connection.
return true
}

private fun requestIsOneShot(e: IOException, userRequest: Request): Boolean {
val requestBody = userRequest.body
return (requestBody != null && requestBody.isOneShot()) ||
e is FileNotFoundException
}

// ...
}
  • 判断是否支持失败重连的机制(默认方式创建的话,retryOnConnectionFailure 属性是 true)
  • requestSendStarted 表明请求是否已经被发送,这里这里为 false
  • 通过 isRecoverable() 方法检测该异常是否是致命的
  • 是否有更多的路线可以重试

isRecoverable(e: IOException, requestSendStarted: Boolean)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
//...

private fun isRecoverable(e: IOException, requestSendStarted: Boolean): Boolean {
// ProtocolException 属于严重异常,不能进行重新连接
// If there was a protocol problem, don't recover.
if (e is ProtocolException) {
return false
}

// 当异常为中断异常时
// If there was an interruption don't recover, but if there was a timeout connecting to a route
// we should try the next route (if there is one).
if (e is InterruptedIOException) {
return e is SocketTimeoutException && !requestSendStarted
}

// 握手异常
// Look for known client-side or negotiation errors that are unlikely to be fixed by trying
// again with a different route.
if (e is SSLHandshakeException) {
// If the problem was a CertificateException from the X509TrustManager,
// do not retry.
if (e.cause is CertificateException) {
return false
}
}
// 验证异常
if (e is SSLPeerUnverifiedException) {
// e.g. a certificate pinning error.
return false
}
// An example of one we might want to retry with a different route is a problem connecting to a
// proxy and would manifest as a standard IOException. Unless it is one we know we should not
// retry, we return true and try a new route.
return true
}

// ...
}
  • 协议问题,不能重试
  • 如果是超时问题,并且请求没有被发送,可以重试,其他的就不要重试了
  • 安全问题,不要重试

Transmitter#canRetry()

1
2
3
4
5
6
7
8
9
10
11
12
class Transmitter(
private val client: OkHttpClient,
private val call: Call
) {
// ...

fun canRetry(): Boolean {
return exchangeFinder!!.hasStreamFailure() && exchangeFinder!!.hasRouteToTry()
}

// ...
}

进到 ExchangeFinder 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class ExchangeFinder(
private val transmitter: Transmitter,
private val connectionPool: RealConnectionPool,
private val address: Address,
private val call: Call,
private val eventListener: EventListener
) {
// ...

/** Returns true if a current route is still good or if there are routes we haven't tried yet. */
fun hasRouteToTry(): Boolean {
synchronized(connectionPool) {
if (nextRouteToTry != null) {
return true
}
if (retryCurrentRoute()) {
// Lock in the route because retryCurrentRoute() is racy and we don't want to call it twice.
nextRouteToTry = transmitter.connection!!.route()
return true
}
return (routeSelection?.hasNext() ?: false) || routeSelector.hasNext()
}
}

/**
* Return true if the route used for the current connection should be retried, even if the
* connection itself is unhealthy. The biggest gotcha here is that we shouldn't reuse routes from
* coalesced connections.
*/

private fun retryCurrentRoute(): Boolean {
return transmitter.connection != null &&
transmitter.connection!!.routeFailureCount == 0 &&
transmitter.connection!!.route().address().url.canReuseConnectionFor(address.url)
}

// ...
}

找相同 address 的链接

followUpRequest(userResponse: Response, route: Route?)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
//...

/**
* Figures out the HTTP request to make in response to receiving `userResponse`. This will
* either add authentication headers, follow redirects or handle a client request timeout. If a
* follow-up is either unnecessary or not applicable, this returns null.
*/

@Throws(IOException::class)
private fun followUpRequest(userResponse: Response, route: Route?): Request? {
val responseCode = userResponse.code()

val method = userResponse.request().method
when (responseCode) {
// 407
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)
}

// 401
HTTP_UNAUTHORIZED -> return client.authenticator().authenticate(route, userResponse)

// 308, 307
HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT -> {
// "If the 307 or 308 status code is received in response to a request other than GET
// or HEAD, the user agent MUST NOT automatically redirect the request"
if (method != "GET" && method != "HEAD") {
return null
}
return buildRedirectRequest(userResponse, method)
}

// 300, 301, 302, 303
HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
return buildRedirectRequest(userResponse, method)
}

// 408
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()
}

// 503
HTTP_UNAVAILABLE -> {
val priorResponse = userResponse.priorResponse()
if (priorResponse != null && priorResponse.code() == HTTP_UNAVAILABLE) {
// We attempted to retry and got another timeout. Give up.
return null
}

if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
// specifically received an instruction to retry without delay
return userResponse.request()
}

return null
}
else -> return null
}
}

// ...
}

当返回码满足某些条件时就重新构造一个 Request,不满足就返回 null

1
2
3
4
5
6
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
transmitter.timeoutEarlyExit()
}
return response
}

当不需要重定向,也就是返回的为 null ,直接返回 response