okhttp学习系列之RetryAndFollowUpInterceptor拦截器

参考文档

简书上 Okhttp的介绍https://zhuanlan.zhihu.com/p/116777864

所有的代码注释已经提交到https://gitee.com/you-zijun/okhttp/commit/db6d553705220faffbfb3e765672bb89254316bb

概述

RetryAndFollowUpInterceptor是OkHttp的中定义的默认拦截器中的第一个,主要负责进行重试和重定向的处理。

  internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    val interceptors = mutableListOf<Interceptor>()
    //先添加用户自定义的拦截器
    interceptors += client.interceptors
    //这里可以看到是第一个拦截器
    interceptors += RetryAndFollowUpInterceptor(client)
    interceptors += BridgeInterceptor(client.cookieJar)
    interceptors += CacheInterceptor(client.cache)
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
      interceptors += client.networkInterceptors
    }
    interceptors += CallServerInterceptor(forWebSocket)

关于他的处理流程,我觉得下图描述的非常准确

下面开始贴代码,走读流程

代码流程走读

该部分代码在https://gitee.com/you-zijun/okhttp/blob/db6d553705220faffbfb3e765672bb89254316bb/okhttp/src/main/kotlin/okhttp3/internal/connection/ExchangeFinder.kt

进去先看到一个while循环

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) {
        //这里主要是一个while循环,在循环里处理异常和重定向请求。
        //每一次捕获异常或收到重定向之后,就重新配置下request,然后continue
        //如果请求成功,则return response 
    }

然后首先是对异常的处理

        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
        }

从上面代码中可以看出,会首先进行网络请求,如果报出异常,则通过recover方法根据异常类型和原始的request进行尝试恢复,如果能够恢复,则continue用修复之后的request重新进行process操作。

如果不能恢复,则直接抛出异常。

我们看下recover的逻辑

  private fun recover(
    e: IOException,
    call: RealCall,
    userRequest: Request,
    requestSendStarted: Boolean
  ): Boolean {
    // The application layer has forbidden retries.
    // 是否手动禁止了重试
    if (!client.retryOnConnectionFailure) return false

    // 检测request是否支持重新发送
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false

    // 通过异常类型来判断是否可以恢复
    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
  }

前面主要根据用户的设置,request当前的状态跑断是否可以重试,主要的逻辑在call.retryAfterFailure

这里补充一点背景,每一个RealCall都配置一个Exchange和ExchangeFinder。每一个Exchange负责处理一个单独的http请求和回复对,并管理ExchangeCodec上的链接,而ExchangeCodec是用来处理真实的http请求的

我们看下retryAfterFailure

  /**
   * Returns true if the current route has a failure that retrying could fix, and that there's
   * a route to retry on.
   *
   * 如果当前路由可以恢复, 或者还有新的路由, 则返回true, 否则返回false
   */
  fun retryAfterFailure(): Boolean {
    if (refusedStreamCount == 0 && connectionShutdownCount == 0 && otherFailureCount == 0) {
      //如果当前连接没有失败, 则返回false
      return false // Nothing to recover from.
    }

    if (nextRouteToTry != null) {
      //如果有新的路由, 则返回true
      return true
    }

    val retryRoute = retryRoute()
    if (retryRoute != null) {
      // Lock in the route because retryRoute() is racy and we don't want to call it twice.
      // 看当前连接的路由是否可以重试, 如果失败次数过多等, 则不重试了
      nextRouteToTry = retryRoute
      return true
    }

    // If we have a routes left, use 'em.
    if (routeSelection?.hasNext() == true) return true

    // If we haven't initialized the route selector yet, assume it'll have at least one route.
    val localRouteSelector = routeSelector ?: return true

    // If we do have a route selector, use its routes.
    return localRouteSelector.hasNext()
  }

上述代码基本上就是重新找一个Route。

 

接下来看重定向和逻辑

        // Attach the prior response if it exists. Such responses never have a body.
        if (priorResponse != null) {
          //我把priorResponse理解成重定向之前的那次response. 如果有, 说明有进行过重定向操作, 将该response返回出去
          response = response.newBuilder()
              .priorResponse(priorResponse.newBuilder()
                  .body(null)
                  .build())
              .build()
        }

        val exchange = call.interceptorScopedExchange
        // 检查是否需要重定向
        val followUp = followUpRequest(response, exchange)

        if (followUp == null) {
          // 如果不需要重定向, 就直接返回response了.
          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")
        }

        // 对于需要followUp的, 进行followUp
        request = followUp
        priorResponse = response

从上面代码中可以看到, 如果不需要进行重定向,就直接返回了response,如果需要,则重新配置request, 进行重新的请求。

关键的逻辑在followUpRequest

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

    val method = userResponse.request.method
    when (responseCode) {
      HTTP_PROXY_AUTH -> {
        // 代理认证请求, 客户端必须使用代理认证自身
        val selectedProxy = route!!.proxy
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          //http模式下是不能接受407的response code的
          throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy")
        }
        return client.proxyAuthenticator.authenticate(route, userResponse)
      }

      //未授权, 未授权客户端访问数据
      HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse)

      //重定向
      // 根据response来构造一个重定向的request
      HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
        return buildRedirectRequest(userResponse, method)
      }

      //请求超时
      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
      }

值得注意的是,这里我们将followUp定义为“重定向”即使是不准确的,因为他还处理超时等相关逻辑

 

其他

有一个新的认知,之前我以为interceptor对象是复用的,但是看到代码之后菜之后,每一个call都会新建一堆interceptor对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值