OkHttp 源码解析(Kotlin版)——RetryAndFollowUpInterceptor

1,201 阅读4分钟

源码

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)
    }
  }
}

image.png

第一步 从拦截连中取出request放入一个死循环中,既然是死循环,那么跳出循环的条件也就是最重要的

image.png 第一个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重试机制的实现,现在再来看重定向机制是如何实现的。 image.png

image.png 来看到followUpRequest方法的注释: 找出HTTP请求响应接收[userResponse]。这将添加认证头,遵循重定向或处理客户端请求超时。如果一个follow-up要么是不必要的,要么是不适用的,返回null。这个方法就是来处理重定向和超时的。

image.png

关于请求重定向这块的源码,必须要对响应码的有一定的了解,所以我们先简单回顾一下响应码的区间和类型

  • 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直接重定向则返回空

然后再对请求头进行封装,返回请求

参考博客

www.jianshu.com/p/40636d32c…

下一篇

juejin.cn/post/700772…