RetryAndFollowUpInterceptor会在失败时进行恢复,并根据需要跟随重定向。如果请求被取消,它可能会抛出 IOException 异常。
通过类的介绍得知其作用:
处理网络请求的重试:当网络请求失败时,RetryAndFollowUpInterceptor 可以根据策略进行自动重试。它会检测请求是否可以安全地重试,并在必要时重新发送请求。
跟进重定向:当服务器返回重定向响应时,RetryAndFollowUpInterceptor 可以自动跟随重定向,并发送新的请求到重定向的目标位置。它负责处理重定向响应并更新请求的目标 URL。
处理请求的取消:如果网络请求被取消,RetryAndFollowUpInterceptor 可能会抛出 IOException 异常,表示请求已被中止。它会在适当的时候检查请求的取消状态,并中断请求的执行。
实现失败恢复机制:RetryAndFollowUpInterceptor 在网络请求过程中,会根据不同的失败情况尝试进行恢复。它可以处理连接失败、通信问题、超时等情况,并根据策略尝试重新发送请求以实现恢复。
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
@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>()
// 死循环,不断请求接口,除非触发return操作
while (true) {
// 每次循环,都会为RealCall的成员变量exchangeFinder 设置新的ExchangeFinder对象
// 如果发生请求过程中异常,则不设置新的exchangeFinder
// this.exchangeFinder = ExchangeFinder(...)
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
var response: Response
var closeActiveExchange = true
try {
if (call.isCanceled()) {
throw IOException("Canceled")
}
try {
// 通过责任链模式,请求一次接口
response = realChain.proceed(request)
// 为重定向请求的RealCall,设置新的exchangeFinder
newExchangeFinder = true
} catch (e: RouteException) {
// 尝试通过链接 route失败,Request未被发送出去
// RouteException:表明通过单个路由连接存在问题。可能用其他协议进行了多次尝试,但都没有成功。
// recover() :表示是否进行再次请求:
// 1、应用层 拒绝重试 ,返回 false : if (!client.retryOnConnectionFailure) return false
// 2、无法再次发送请求体。返回 false : if (requestSendStarted && requestIsOneShot(e, userRequest)) return false
// 3、致命的异常。返回 false : InterruptedIOException、SSLHandshakeException、SSLPeerUnverifiedException
// 4、没有更多的 routers 可以尝试 : if (!call.retryAfterFailure()) return false
if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
// 发生异常错误 直接抛出
throw e.firstConnectException.withSuppressed(recoveredFailures)
} else {
// 添加到异常恢复列表中继续恢复
recoveredFailures += e.firstConnectException
}
newExchangeFinder = false
// 再次请求
continue
} catch (e: IOException) {
// 与服务端通讯失败, 请求已经被发送
// 同上
if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
throw e.withSuppressed(recoveredFailures)
} else {
// 添加到异常恢复列表中继续恢复
recoveredFailures += e
}
newExchangeFinder = false
// 再次请求
continue
}
// ---------------------↑↑↑↑↑↑以上就是请求错误的重试逻辑↑↑↑↑↑↑↑↑--------------
// 如果已经存在重定向响应,则 该 response不用应该有 body
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build()
}
val exchange = call.interceptorScopedExchange
// 重定向的核心逻辑方法,通过之前请求的response 获得 新的重定向Request
// 1、通过 response.code 判断是否符合重定向(30X,)、Unauthorized(401)...
// 2、例如30X的重定向,则会获取 响应头中的 "Local"字段获取新的URL
val followUp = followUpRequest(response, exchange)
// 不存在重定向请求,直接返回 response
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()
// 累计重定向次数 大于20(默认)次 抛出异常
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
// 重置 request 和 response
request = followUp
priorResponse = response
// 继续下一次新的重定向请求
} finally {
// 退出网络拦截器 交换流
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
总结:请求失败后,会重试3次请求? 从源码中怎么体现出来的呢??? 啊 啊 啊 。。。。
BridgeInterceptor主要是将应用程序代码转化为网络代码的桥梁。
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val userRequest = chain.request()
val requestBuilder = userRequest.newBuilder()
val body = userRequest.body
// 对设置body的请求进行处理。Post、PUT、PATCH等
if (body != null) {
val contentType = body.contentType()
if (contentType != null) {
// Content-Type 用于指示请求或响应的实体的媒体类型,描述了实体是什么类型的文档,以及如何解析它
// 可以指定实体是HTML文档、JSON数据、XML文档、图像文件等。
// 该头部的值通常由媒体类型(例如 text/html,application/json等)和字符集(例如 utf-8)组成。
requestBuilder.header("Content-Type", contentType.toString())
}
// 请求头 Content-Length 表示请求体的字节数。
// 它是一个十进制数,通常用于 POST 和 PUT 请求,用于指定请求体的大小。
val contentLength = body.contentLength()
if (contentLength != -1L) {
requestBuilder.header("Content-Length", contentLength.toString())
// Transfer-Encoding 用于指定消息主体的传输编码方式。
// 它通常用于支持以流的形式传输响应主体,而无需预先知道其总大小。
requestBuilder.removeHeader("Transfer-Encoding")
} else {
// 常见的传输编码方式包括 chunked 和 compress。
// 其中 chunked 表示在传输响应主体时会将其分成多个块,每个块的大小在发送前不需要知道,
// 以此来支持传输较大的响应。
requestBuilder.header("Transfer-Encoding", "chunked")
requestBuilder.removeHeader("Content-Length")
}
}
if (userRequest.header("Host") == null) {
// 请求头 Host 表示 HTTP 请求的目标服务器的主机名和端口号。这个请求头在 HTTP/1.1 协议中是必需的,
// 并且必须包含在所有请求中。它的作用是告诉服务器请求的目标主机地址和端口号。
requestBuilder.header("Host", userRequest.url.toHostHeader())
}
if (userRequest.header("Connection") == null) {
// 请求头 Connection 是用来指定与服务器建立连接的选项,主要包括以下几个常用选项:
// -keep-alive:表示请求完成后,仍然保持连接状态,以便下次请求可以复用这个连接。
// -close:表示请求完成后立即关闭连接。
// 对于HTTP/1.1 协议,默认的 Connection 值是 keep-alive,表示在完成请求后仍然保持连接状态。
// 而对于HTTP/1.0 协议,默认的 Connection 值是 close,表示完成请求后关闭连接。
requestBuilder.header("Connection", "Keep-Alive")
}
// 当我们在请求头中添加 "Accept-Encoding: gzip" 字段时,我们也需要负责解压缩传输流。
// 这是因为 "gzip" 是一种常用的压缩算法,
// 如果服务端响应的内容使用了 gzip 压缩,那么就需要客户端进行解压缩才能正确地处理数据。
// 在请求头中指定了支持 gzip 压缩,服务器就有可能使用 gzip 进行压缩,客户端也需要相应地对压缩过的数据进行解压缩。
var transparentGzip = false
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
// 服务器可以根据 Accept-Encoding 的值,将响应数据进行相应的压缩后再返回给客户端,以减少网络传输流量,提高传输效率。
// 常见的编码方式有 gzip、deflate 等
transparentGzip = true
requestBuilder.header("Accept-Encoding", "gzip")
}
// 通过URL获取cookie
// Cookie是一种用于存储会话信息或用户状态的机制。当客户端发送请求时,
// 如果请求头中包含了Cookie信息,那么服务端就能通过这些信息识别出请求来自哪个用户,并且可以根据这些信息返回对应的响应。
val cookies = cookieJar.loadForRequest(userRequest.url)
if (cookies.isNotEmpty()) {
// 在请求头里面添加cookie
// 简单的样例:Cookie: session_id=1234567890abcdef; user_id=123
requestBuilder.header("Cookie", cookieHeader(cookies))
}
// 用于标识客户端发送请求时所使用的浏览器或客户端类型、操作系统、软件版本等信息。
// 服务器可以根据 User-Agent 的值来判断客户端类型,从而进行相应的处理和响应。
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", userAgent)
}
// 获取到请求 响应response
val networkResponse = chain.proceed(requestBuilder.build())
// 解析cookie并保存
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
// ??? 为什么重新构建一个ResponseBuild
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
// 解压缩传输流
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
// 如果响应头和状态 表示 该响应具有(可能是0长度的)正文,则返回true。
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
// 去除 Content-Encoding 、Content-Length
val strippedHeaders = networkResponse.headers.newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build()
responseBuilder.headers(strippedHeaders)
val contentType = networkResponse.header("Content-Type")
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
// 将重建的response 创建
return responseBuilder.build()
}
小结:Bridge拦截器就是将用户的请求转换包装转化成网络请求,主要对请求头、请求体、请求方法的解析转换,默认添加了一些请求头!
其次对response根据Header的描述进行解析。