转载

OkHttp - RetryAndFollowUpInterceptor 源码简析

Github: okhttp 分析版本: 930d4d0

This interceptor recovers from failures and follows redirects as necessary

intercept(chain: Interceptor.Chain)

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)

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
  }
  
  // ...
}
requestSendStarted
isRecoverable()

isRecoverable(e: IOException, requestSendStarted: Boolean)

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

class Transmitter(
  private val client: OkHttpClient,
  private val call: Call
) {
  // ...
  
  fun canRetry(): Boolean {
    return exchangeFinder!!.hasStreamFailure() && exchangeFinder!!.hasRouteToTry()
  }
  
  // ...
}

进到 ExchangeFinder 中:

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

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

if (followUp == null) {
  if (exchange != null && exchange.isDuplex) {
    transmitter.timeoutEarlyExit()
  }
  return response
}

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

原文  http://yydcdut.com/2019/07/08/okhttp-retry-and-follow-up-interceptor-analyse/
正文到此结束
Loading...