okhttp详解系列二:重试重定向拦截器

重试和重定向拦截器主要负责三件事:1) 出错后判断是否重试;2) 处理重定向;3) 处理用户认证(401和407响应码);

来看下源码:

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
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>()
// 1、重试和重定向的场景依然是在while循环中,不会退出该拦截器
while (true) {
// 2、根据需要创建 ExchangeFinder
call.enterNetworkInterceptorExchange(request, newExchangeFinder)

var response: Response
var closeActiveExchange = true
try {
if (call.isCanceled()) {
throw IOException("Canceled")
}

try {
// 3、realChain指向下一个拦截器,发生重试或者重定向后,
// 会从下一个拦截器开始执行,前面的应用拦截器和此拦截器都不会被执行了
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
// 4、错误可以恢复,继续while循环
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
// 5、错误可以恢复,继续while循环
continue
}

......

val exchange = call.interceptorScopedExchange
// 6、followUpRequest是判断是否重试的主要逻辑,包括重定向、超时重试、用户认证等场景
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)
}
}
}

应用拦截器只会被执行一次的原因

我们都知道okhttp支持两种类型的用户层拦截器:应用拦截器(Application Interceptors)和网络拦截器(Network Interceptors)。应用拦截器每次发起请求都会被保证执行一次,即使发生了重试和重定向也是被执行一次。而网络拦截器在重试和重定向的场景都会被执行多次,而且缓存的场景可能不会被执行。这种差异是什么原因导致的呢?

RetryAndFollowUpInterceptor拦截器使用了一个while循环,当需要重试或者重定向时,不会退出循环,继续执行chain.proceed(),这个chain就是RealInterceptorChain。在分析RealInterceptorChain的proceed()方法时,我们知道每次执行下一个拦截器时,都会把RealInterceptorChain拷贝一份,chain的index就指向下一个拦截器。所以重试和重定向的情况都会从下一个拦截器开始,而用户注册的程序拦截器在RetryAndFollowUpInterceptor前面,这就是即使发生了重试和重定向程序拦截器也会只被执行一次的根本原因。而网络拦截器排在RetryAndFollowUpInterceptor后面,所以会被执行多次。

参考文章

  1. HTTP 身份验证