加油吧,打工人,不相信命运,只相信自己!

简介

Okhttp的封装了一些列恳求所需求的参数,不管是同步恳求还是异步恳求最终都会经过五大拦截器的处理才能得到服务器回来的恳求成果。本篇文章首要解说Okhttp五大拦截器的重试重定向拦截器的作用。

RetryAndFollowUpInterceptor拦截器作为OKhttp的第一个默许拦截器,首要作用是当客户端网络恳求失利时或目标响应的方位产生改动时调用。

深化解读OKhttp五大拦截器之RetryAndFollowUpInterceptor

一,网络失利重试

 val realChain = chain as RealInterceptorChain //恳求链
    var request = chain.request //网络恳求
    val call = realChain.call //call目标
    var followUpCount = 0 //重试次数初始为0
    var priorResponse: Response? = null //以前的回来值
    var newExchangeFinder = true
    var recoveredFailures = listOf<IOException>()
    //默许进入while死循环
    while (true) {
     //获取在链接池中网络链接
      call.enterNetworkInterceptorExchange(request, newExchangeFinder)
  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) { //IO反常
          // An attempt to communicate with a server failed. The request may have been sent.
            //假如是因为IO反常,那么requestSendStarted=true 
          if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
            throw e.withSuppressed(recoveredFailures)
          } else {
            recoveredFailures  = e
          }
          newExchangeFinder = false
          continue
        }
        request = followUp
        priorResponse = response
      } finally {
        call.exitNetworkInterceptorExchange(closeActiveExchange)
      }
 }
}

从源码中能够看出 履行RetryAndFollowUpInterceptor拦截器时,默许进入while死循环,表明网络恳求失利了能够一直重试,直到realChain.proceed(request)回来服务端数据。

那什么时候中止重试呢,总不能一直进行网络重试吧,从try-catch中能够看到当产生RouterException和IOException时,经过recover函数判别来抛出反常来中止while循环。所以recover函数才是决议是否重试的关键。

下面剖析recover函数是怎么决议网络恳求是否需求重试的。

  private fun recover(
    e: IOException,
    call: RealCall,
    userRequest: Request,
    requestSendStarted: Boolean
  ): Boolean {
    // The application layer has forbidden retries.
    //1. okhttpclient装备不重试参数 
    if (!client.retryOnConnectionFailure) return false
    // We can't send the request body again.
    // 2. 不重试:
    // 条件1.假如是IO反常(非http2中止反常)表明恳求或许宣布
    // 条件2、假如恳求体只能被运用一次(默许为false)
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false
    // This exception is fatal.
    // 3.反常不重试:协议反常、IO中止反常(除Socket读写超时之外),ssl认证反常
    if (!isRecoverable(e, requestSendStarted)) return false
    // No more routes to attempt.
    //4. 是否有更多的恳求道路
    if (!call.retryAfterFailure()) return false
    // For failure recovery, use the same route selector with a new connection.
    return true
  }
  1. client.retryOnConnectionFailure 为构建okhttpClient时的装备参数,默许为true。
  2. requestSendStarted 表明网络恳求现已发送出去了。requestIsOneShot 用户恳求是否只履行一次。
 private fun requestIsOneShot(e: IOException, userRequest: Request): Boolean {
    val requestBody = userRequest.body
    // 1. 恳求体不为null
    // 2.默许为false
     //3.文件不存在.比方上传文件时本地文件被删除了
    return (requestBody != null && requestBody.isOneShot()) ||
        e is FileNotFoundException
  }
  1. isRecoverable() 产生如下四种类型反常不会重试:协议反常;中止反常时恳求现已宣布时;证书反常;证书验证反常。
  private fun isRecoverable(e: IOException, requestSendStarted: Boolean): Boolean {
    // 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
  }
  1. retryAfterFailure 回来false时不重试。当没有更多的重试道路时,不能进行重试。

总述所示,网络恳求中止重试的条件一共有四种。

  • 用户初始化Okhttp时的参数装备
  • 网络恳求被中止产生中止反常且客户端的网络恳求,恳求衔接超时且还没有发送出去。
  • 协议反常,数字证书SSL反常和验证失利反常
  • 没有剩余的重试恳求道路

二, 网络重定向

当网络恳求成功后,得到服务端的回来值response,客户端还需求对response进行一些验证。

if (priorResponse != null) {
          //将上次的恳求成果作为这边恳求response的参数
          response = response.newBuilder()
              .priorResponse(priorResponse.newBuilder()
                  .body(null)
                  .build())
              .build()
        }
        val exchange = call.interceptorScopedExchange
        //判别是否构建重定向恳求
        val followUp = followUpRequest(response, exchange)
        //假如不需求重定向恳求
        if (followUp == null) {
          //假如衔接是全双工 websocket 则退出超时。
          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 = followUp
        priorResponse = response
      } finally {
        call.exitNetworkInterceptorExchange(closeActiveExchange)
      }
    }

当客户端拿到服务端的响应时,还需求进行响应的response进行验证来决议是否需求重定向。从源码中能够看出首要是经过followUpRequest()判别。

下面深化源码查看一些followUpRequest是怎么判别的。

 private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
    //衔接路由 
    val route = exchange?.connection?.route()
    //响应码
    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)
      // 30... 临时重定向
      HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, 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
      }
      //421 衔接产生错误
      HTTP_MISDIRECTED_REQUEST -> {
        // OkHttp can coalesce HTTP/2 connections even if the domain names are different. See
        // RealConnection.isEligible(). If we attempted this and the server returned HTTP 421, then
        // we can retry on a different connection.
        val requestBody = userResponse.request.body
        if (requestBody != null && requestBody.isOneShot()) {
          return null
        }
        if (exchange == null || !exchange.isCoalescedConnection) {
          return null
        }
        exchange.connection.noCoalescedConnections()
        return userResponse.request
      }
      else -> return null
    }
  }

从源码剖析中能够得知,当客户端产生衔接反常,或服务端不行用时 时才会禁止客户端进行重定向。假如恳求产生重定向,最大重定向的次数只要20次。

经过对RetryAndFollowUpInterceptor重试重定向拦截器的源码剖析,能够更好的帮助了解网络衔接恳求的实现里,对日常工作中解决网络问题及优化供给帮助。