Android进阶宝典 -- OkHttp五大拦截分析

相关文章:

Android进阶宝典 -- OkHttp3源码分析(Kotlin版本分发器和拦截器)

在之前的文章中,详细介绍了OkHttp网络请求的主流程分析,包括基本的使用以及同步/异步网络请求逻辑,那么本节将会继续深入OkHttp拦截器,看请求是如何与服务器完成通信的。

1 五大拦截器

首先我们先看下,异步请求时拦截器处理逻辑:

kotlin 复制代码
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)
  
  val chain = RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this,
      client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)

  var calledNoMoreExchanges = false
  try {
    val response = chain.proceed(originalRequest)
    if (transmitter.isCanceled) {
      response.closeQuietly()
      throw IOException("Canceled")
    }
    return response
  } catch (e: IOException) {
    calledNoMoreExchanges = true
    throw transmitter.noMoreExchanges(e) as Throwable
  } finally {
    if (!calledNoMoreExchanges) {
      transmitter.noMoreExchanges(null)
    }
  }
}

除去自定义的拦截器,剩余的拦截器有:RetryAndFollowUpInterceptor BridgeInterceptor CacheInterceptor ConnectInterceptor CallServerInterceptor,他们被统一放在了一个Interceptor集合中,并被封装成了RealInterceptorChain对象,并执行了proceed方法。

java 复制代码
fun proceed(request: Request, transmitter: Transmitter, exchange: Exchange?): Response {

  // Call the next interceptor in the chain.
  // 主要就是将index++;
  val next = RealInterceptorChain(interceptors, transmitter, exchange,
      index + 1, request, call, connectTimeout, readTimeout, writeTimeout)
      
  // 取出当前位置的拦截器
  val interceptor = interceptors[index]

  @Suppress("USELESS_ELVIS")
  // 执行拦截器的intercept方法,同时传入下一个拦截器
  val response = interceptor.intercept(next) ?: throw NullPointerException(
      "interceptor $interceptor returned null")

  // Confirm that the next interceptor made its required call to chain.proceed().
  check(exchange == null || index + 1 >= interceptors.size || next.calls == 1) {
    "network interceptor $interceptor must call proceed() exactly once"
  }

  check(response.body != null) { "interceptor $interceptor returned a response with no body" }

  return response
}

在proceed方法中,首先构建了一个新的RealInterceptChain对象next,唯一的变化就是index做了自增处理,同时取出当前index的拦截器,执行了其intercept方法,同时把next作为参数传进去。

假如当前拦截器为RetryAndFollowUpInterceptor,那么在RetryAndFollowUpInterceptor中,会执行intercept方法,在执行proceed方法后,会拿到下一个拦截器BridgeInterceptor并执行其intercept方法。

如此一来,每个拦截器都会按顺序执行,同时把response从底层回传到上层。

2 RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor是重试/重定向拦截器,在intercept方法中,我们看到当前拦截器并没有对请求做任何处理,直接传递到下一级拦截器中,所以RetryAndFollowUpInterceptor做的主要工作就是处理异常重试。

kotlin 复制代码
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) {
    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) {
      // 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
    } catch (e: IOException) {
      // 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()
      }
    }

    // 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)
    
    // 如果为空,说明不需要重定向
    if (followUp == null) {
      if (exchange != null && exchange.isDuplex) {
        transmitter.timeoutEarlyExit()
      }
      return response
    }

    val followUpBody = followUp.body
    if (followUpBody != null && followUpBody.isOneShot()) {
      return response
    }

    response.body?.closeQuietly()
    if (transmitter.hasExchange()) {
      exchange?.detachWithViolence()
    }
    
    // 超出重定向的次数也不可以,直接抛异常。
    if (++followUpCount > MAX_FOLLOW_UPS) {
      throw ProtocolException("Too many follow-up requests: $followUpCount")
    }

    request = followUp
    priorResponse = response
  }
}

其重试逻辑就是通过while死循环,来捕获后续的请求过程中的异常,因此我们需要关注catch住的两个异常,分别是为RouteExceptionIOException

2.1 可以重试的条件

当捕获到异常的时候,并不代表一定会发起重试,这里面会细分很多的场景。

例如捕获到了RouteException,在recover方法中会判断是否支持重试,如果不支持重试,那么就直接抛出异常交给上层处理。

kotlin 复制代码
private fun recover(
  e: IOException,
  transmitter: Transmitter,
  requestSendStarted: Boolean,
  userRequest: Request
): 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 (!transmitter.canRetry()) return false
  // For failure recovery, use the same route selector with a new connection.
  return true
}

那么什么场景下不支持重试呢?

  • OkHttpClinet初始化时,retryOnConnectionFailure属性设置为false,此时不允许错误重试,默认情况下为true,支持重试。
  • 当使用post请求时,请求体类型不支持重试;
  • 当发生协议错误、Socket超时、SSL证书错误时,不支持重试;
  • 当没有多余的路由时,不支持重试。

2.2 重定向处理

当请求成功,拿到response之后,会调用followUpRequest方法判断是否需要重定向,如果返回的request为空,则不需要重定向,直接返回响应即可。

如果返回的request不为空,则代表需要进行重定向,需要拿到重定向的request再次向服务端发起请求,但重定向的次数有限,默认是20次。

kotlin 复制代码
private fun followUpRequest(userResponse: Response, route: Route?): Request? {
  val responseCode = userResponse.code

  val method = userResponse.request.method
  when (responseCode) {
    // 307 308
    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 - 30x
   
    HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
      return buildRedirectRequest(userResponse, method)
    }
    // ......
    
    else -> return null
  }
}

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.
  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 maintainBody = HttpMethod.redirectsWithBody(method)
    if (HttpMethod.redirectsToGet(method)) {
      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()
}

判断重定向是通过响应code,如果是30x开头的状态码,那么就需要从响应头的Location字段中取出重定向的地址,并重新构造新的请求。

3 BridgeInterceptor

BridgeInterceptor 相对来说比较简单,主要是补全请求头的参数,这里会在请求头中声明响应体要用gzip压缩,同时在拿到响应之后,通过gzip解压。

在之前的文章中介绍过,gzip能够最大化的压缩数据,同时提高传输效率。

kotlin 复制代码
override fun intercept(chain: Interceptor.Chain): Response {
  val userRequest = chain.request()
  val requestBuilder = userRequest.newBuilder()

  val body = userRequest.body
  if (body != null) {
    val contentType = body.contentType()
    if (contentType != null) {
      requestBuilder.header("Content-Type", contentType.toString())
    }

    val contentLength = body.contentLength()
    if (contentLength != -1L) {
      requestBuilder.header("Content-Length", contentLength.toString())
      requestBuilder.removeHeader("Transfer-Encoding")
    } else {
      requestBuilder.header("Transfer-Encoding", "chunked")
      requestBuilder.removeHeader("Content-Length")
    }
  }

  if (userRequest.header("Host") == null) {
    requestBuilder.header("Host", userRequest.url.toHostHeader())
  }

  if (userRequest.header("Connection") == null) {
    requestBuilder.header("Connection", "Keep-Alive")
  }

  // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
  // the transfer stream.
  var transparentGzip = false
  if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
    transparentGzip = true
    requestBuilder.header("Accept-Encoding", "gzip")
  }

  val cookies = cookieJar.loadForRequest(userRequest.url)
  if (cookies.isNotEmpty()) {
    requestBuilder.header("Cookie", cookieHeader(cookies))
  }

  if (userRequest.header("User-Agent") == null) {
    requestBuilder.header("User-Agent", userAgent)
  }

  val networkResponse = chain.proceed(requestBuilder.build())

  cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)

  val responseBuilder = networkResponse.newBuilder()
      .request(userRequest)

  if (transparentGzip &&
      "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
      networkResponse.promisesBody()) {
    val responseBody = networkResponse.body
    if (responseBody != null) {
      val gzipSource = GzipSource(responseBody.source())
      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()))
    }
  }

  return responseBuilder.build()
}

4 CacheInterceptor

CacheInterceptor是OkHttp中与缓存相关的拦截器,用于存储一次请求的响应数据,并持久化存储在本地。这里要注意,缓存只有GET请求才有缓存,因为是拿请求的url作为key。

kotlin 复制代码
OkHttpClient.Builder()
    .cache(Cache(File("xxx"), 24 * 1024 * 1024))
    .retryOnConnectionFailure(false)
    .build()

如果想要使用缓存,那么必须在OkHttpClient初始化时声明,否则默认cache字段为空。

kotlin 复制代码
class CacheInterceptor(internal val cache: Cache?) : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
    
      //从缓存中拿数据
      
      val cacheCandidate = cache?.get(chain.request())

      val now = System.currentTimeMillis()

      val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
      
      // 策略对象1 - 网络请求
      val networkRequest = strategy.networkRequest
      
      // 策略对象2 - 响应缓存
      val cacheResponse = strategy.cacheResponse

      cache?.trackResponse(strategy)

      if (cacheCandidate != null && cacheResponse == null) {
        // The cache candidate wasn't applicable. Close it.
        cacheCandidate.body?.closeQuietly()
      }

      // If we're forbidden from using the network and the cache is insufficient, fail.
      if (networkRequest == null && cacheResponse == null) {
        return Response.Builder()
            .request(chain.request())
            .protocol(Protocol.HTTP_1_1)
            .code(HTTP_GATEWAY_TIMEOUT) //直接504
            .message("Unsatisfiable Request (only-if-cached)")
            .body(EMPTY_RESPONSE)
            .sentRequestAtMillis(-1L)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build()
      }

      // If we don't need the network, we're done.
      //直接用缓存中的数据
      if (networkRequest == null) {
        return cacheResponse!!.newBuilder()
            .cacheResponse(stripBody(cacheResponse))
            .build()
      }

      var networkResponse: Response? = null
      try {
        networkResponse = chain.proceed(networkRequest)
      } finally {
        // If we're crashing on I/O or otherwise, don't leak the cache body.
        if (networkResponse == null && cacheCandidate != null) {
          cacheCandidate.body?.closeQuietly()
        }
      }

      // If we have a cache response too, then we're doing a conditional get.
      if (cacheResponse != null) {
        if (networkResponse?.code == HTTP_NOT_MODIFIED) {
          val response = cacheResponse.newBuilder()
              .headers(combine(cacheResponse.headers, networkResponse.headers))
              .sentRequestAtMillis(networkResponse.sentRequestAtMillis)
              .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
              .cacheResponse(stripBody(cacheResponse))
              .networkResponse(stripBody(networkResponse))
              .build()

          networkResponse.body!!.close()

          // Update the cache after combining headers but before stripping the
          // Content-Encoding header (as performed by initContentStream()).
          cache!!.trackConditionalCacheHit()
          cache.update(cacheResponse, response)
          return response
        } else {
          cacheResponse.body?.closeQuietly()
        }
      }

      // cacheResponse为空,但是networkRequest不为空
      
      val response = networkResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .networkResponse(stripBody(networkResponse))
          .build()

      if (cache != null) {
        if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
          // Offer this request to the cache.
          val cacheRequest = cache.put(response)
          return cacheWritingResponse(cacheRequest, response)
        }
        
        // 如果是post请求,那么就直接将缓存移除
        
        if (HttpMethod.invalidatesCache(networkRequest.method)) {
          try {
            cache.remove(networkRequest)
          } catch (_: IOException) {
            // The cache cannot be written.
          }
        }
      }

      return response
    }
}    

4.1 用缓存还是发起网络请求?

当我们从缓存中拿到数据之后,我们可以直接用吗?当然不可以,如果接口的数据没有发生变化,那么可以直接用,如果接口的数据发生变化,那么就不能直接用缓存的数据。所以如何判断需要从缓存中拿数据,还是从网络拿数据,就需要CacheStrategy中的两个成员变量来看。

kotlin 复制代码
class CacheStrategy internal constructor(
  /** The request to send on the network, or null if this call doesn't use the network. */
  val networkRequest: Request?,
  /** The cached response to return or validate; or null if this call doesn't use a cache. */
  val cacheResponse: Response?
) 

如果networkRequest为null,那么就不需要发起网络请求;如果cacheResponse为null,那么就不能用缓存中的数据;反之可以使用。

  • 如果networkRequest为空,且cacheResponse也为空,那么此次请求失败,直接返回504的响应code,在RetryAndFollowUpInterceptor拦截器中处理异常。
  • 如果networkRequest为空,且cacheResponse不为空,那么就直接将缓存中的数据返回即可。
  • 如果networkRequest不为空,且cacheResponse为空,那么就需要使用网络请求拿到的数据,并将响应数据加到缓存中。
  • 如果networkRequest不为空,且cacheResponse不为空,那么就发起请求,此时返回的响应code可能为304(与上次请求的数据无变化),那么就更新缓存直接返回即可。

4.2 如何判断用缓存还是网络数据

这个问题,就需要看CacheStrategy的内部逻辑。

kotlin 复制代码
/** Returns a strategy to use assuming the request can use the network. */
private fun computeCandidate(): CacheStrategy {
  // No cached response.
  if (cacheResponse == null) {
    return CacheStrategy(request, null)
  }

  // Drop the cached response if it's missing a required handshake.
  if (request.isHttps && cacheResponse.handshake == null) {
    return CacheStrategy(request, null)
  }

  // If this response shouldn't have been stored, it should never be used as a response source.
  // This check should be redundant as long as the persistence store is well-behaved and the
  // rules are constant.
  if (!isCacheable(cacheResponse, request)) {
    return CacheStrategy(request, null)
  }

  val requestCaching = request.cacheControl
  if (requestCaching.noCache || hasConditions(request)) {
    return CacheStrategy(request, null)
  }

  val responseCaching = cacheResponse.cacheControl

  val ageMillis = cacheResponseAge()
  var freshMillis = computeFreshnessLifetime()

  if (requestCaching.maxAgeSeconds != -1) {
    freshMillis = minOf(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds.toLong()))
  }

  var minFreshMillis: Long = 0
  if (requestCaching.minFreshSeconds != -1) {
    minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
  }

  var maxStaleMillis: Long = 0
  if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
    maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
  }

  if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
    val builder = cacheResponse.newBuilder()
    if (ageMillis + minFreshMillis >= freshMillis) {
      builder.addHeader("Warning", "110 HttpURLConnection "Response is stale"")
    }
    val oneDayMillis = 24 * 60 * 60 * 1000L
    if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
      builder.addHeader("Warning", "113 HttpURLConnection "Heuristic expiration"")
    }
    return CacheStrategy(null, builder.build())
  }

  // Find a condition to add to the request. If the condition is satisfied, the response body
  // will not be transmitted.
  val conditionName: String
  val conditionValue: String?
  when {
    etag != null -> {
      conditionName = "If-None-Match"
      conditionValue = etag
    }

    lastModified != null -> {
      conditionName = "If-Modified-Since"
      conditionValue = lastModifiedString
    }

    servedDate != null -> {
      conditionName = "If-Modified-Since"
      conditionValue = servedDateString
    }

    else -> return CacheStrategy(request, null) // No condition! Make a regular request.
  }

  val conditionalRequestHeaders = request.headers.newBuilder()
  conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)

  val conditionalRequest = request.newBuilder()
      .headers(conditionalRequestHeaders.build())
      .build()
  return CacheStrategy(conditionalRequest, cacheResponse)
}

这部分需要对https的缓存非常熟悉,包括请求头中的If-None-MatchIf-Modified-Since,来判断缓存是否有效,以及判断服务器的数据是否发生了改变,来决定缓存是否可以使用,如果有精力的伙伴可以对缓存这一部分进行详细的了解。

5 ConnectInterceptor

ConnectInterceptor的主要作用就是与目标服务器建立连接,这里我们就需要了解OkHttp中关于连接池的相关原理性的问题。

kotlin 复制代码
/** Opens a connection to the target server and proceeds to the next interceptor. */
object ConnectInterceptor : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val request = realChain.request()
    val transmitter = realChain.transmitter()

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    val doExtensiveHealthChecks = request.method != "GET"
    val exchange = transmitter.newExchange(chain, doExtensiveHealthChecks)

    return realChain.proceed(request, transmitter, exchange)
  }
}

5.1 连接池以及复用

为什么需要连接池,上层在使用https请求时,底层的基础协议依然是tcp协议,需要三次握手和四次挥手,如果每次请求完毕都关闭连接,那么下次请求需要重新握手,效率太低,因此OkHttp中采用了连接池复用的方案解决此问题。

kotlin 复制代码
class ConnectionPool(
  maxIdleConnections: Int,
  keepAliveDuration: Long,
  timeUnit: TimeUnit
) {
  internal val delegate = RealConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit)

  constructor() : this(5, 5, TimeUnit.MINUTES)

  /** Returns the number of idle connections in the pool. */
  fun idleConnectionCount(): Int = delegate.idleConnectionCount()

  /** Returns total number of connections in the pool. */
  fun connectionCount(): Int = delegate.connectionCount()

  /** Close and remove all idle connections in the pool. */
  fun evictAll() {
    delegate.evictAll()
  }
}

OkHttp中的连接池为ConnectionPool,外部通过delegate来使用,默认最大的空闲连接数为5,连接时长为5分钟。

kotlin 复制代码
fun put(connection: RealConnection) {
  assert(Thread.holdsLock(this))
  if (!cleanupRunning) {
    cleanupRunning = true
    executor.execute(cleanupRunnable)
  }
  connections.add(connection)
}

每次往连接池中加入连接的时候,都会执行清理任务,首先会循环遍历连接池中的集合,看空闲的连接和正在使用的连接的个数,并且会找到一个空闲时间最久的连接。

java 复制代码
fun cleanup(now: Long): Long {
  var inUseConnectionCount = 0
  var idleConnectionCount = 0
  var longestIdleConnection: RealConnection? = null
  var longestIdleDurationNs = Long.MIN_VALUE

  // Find either a connection to evict, or the time that the next eviction is due.
  synchronized(this) {
    for (connection in connections) {
      // If the connection is in use, keep searching.
      if (pruneAndGetAllocationCount(connection, now) > 0) {
        inUseConnectionCount++
        continue
      }

      idleConnectionCount++

      // If the connection is ready to be evicted, we're done.
      val idleDurationNs = now - connection.idleAtNanos
      if (idleDurationNs > longestIdleDurationNs) {
        longestIdleDurationNs = idleDurationNs
        longestIdleConnection = connection
      }
    }

    when {
      longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections -> {
        // We've found a connection to evict. Remove it from the list, then close it below
        // (outside of the synchronized block).
        connections.remove(longestIdleConnection)
      }
      idleConnectionCount > 0 -> {
        // A connection will be ready to evict soon.
        return keepAliveDurationNs - longestIdleDurationNs
      }
      inUseConnectionCount > 0 -> {
        // All connections are in use. It'll be at least the keep alive duration 'til we run
        // again.
        return keepAliveDurationNs
      }
      else -> {
        // No connections, idle or in use.
        cleanupRunning = false
        return -1
      }
    }
  }

  longestIdleConnection!!.socket().closeQuietly()

  // Cleanup again immediately.
  return 0
}

遍历完成之后,会判断空闲时间最久的这个链接是否超过了5min(最长空闲时间),或者判断空闲连接个数是否超过了5个,如果是就直接移除,并关闭socket。

当需要从连接池中取出连接的时候,需要判断此次请求的域名、路由之类的属性是否一致,如果保持一致,那么就可以复用连接池中的连接。

kotlin 复制代码
fun transmitterAcquirePooledConnection(
  address: Address,
  transmitter: Transmitter,
  routes: List<Route>?,
  requireMultiplexed: Boolean
): Boolean {
  assert(Thread.holdsLock(this))
  for (connection in connections) {
    if (requireMultiplexed && !connection.isMultiplexed) continue
    if (!connection.isEligible(address, routes)) continue
    transmitter.acquireConnectionNoEvents(connection)
    return true
  }
  return false
}

如果没有匹配到,那么就可以新建一个RealConnection连接,放到连接池中。

6 CallServerInterceptor

这是责任链的最后一环,在ConnectInterceptor中与目标服务器建立socket连接后(无论是从连接池中拿到的,还是新建的),在CallServerInterceptor中就是将请求发送到服务器,拿到服务端返回的响应,往回抛。

kotlin 复制代码
class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.exchange()
    val request = realChain.request()
    val requestBody = request.body
    val sentRequestMillis = System.currentTimeMillis()

    exchange.writeRequestHeaders(request)

    var responseHeadersStarted = false
    var responseBuilder: Response.Builder? = null
    if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return
      // what we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
        //先把请求头发出去
        exchange.flushRequest()
        responseHeadersStarted = true
        exchange.responseHeadersStart()
        responseBuilder = exchange.readResponseHeaders(true)
      }
      // 收到了服务端100的响应
      if (responseBuilder == null) {
        if (requestBody.isDuplex()) {
          // Prepare a duplex body so that the application can send a request body later.
          exchange.flushRequest()
          val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
          requestBody.writeTo(bufferedRequestBody)
        } else {
          // Write the request body if the "Expect: 100-continue" expectation was met.
          val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
          requestBody.writeTo(bufferedRequestBody)
          bufferedRequestBody.close()
        }
      } else {
        exchange.noRequestBody()
        if (!exchange.connection()!!.isMultiplexed) {
          // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
          // from being reused. Otherwise we're still obligated to transmit the request body to
          // leave the connection in a consistent state.
          exchange.noNewExchangesOnConnection()
        }
      }
    } else {
      exchange.noRequestBody()
    }

    if (requestBody == null || !requestBody.isDuplex()) {
      exchange.finishRequest()
    }
    if (!responseHeadersStarted) {
      exchange.responseHeadersStart()
    }
    if (responseBuilder == null) {
      responseBuilder = exchange.readResponseHeaders(false)!!
    }
    var response = responseBuilder
        .request(request)
        .handshake(exchange.connection()!!.handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build()
    var code = response.code
    if (code == 100) {
      // server sent a 100-continue even though we did not request one.
      // try again to read the actual response
      response = exchange.readResponseHeaders(false)!!
          .request(request)
          .handshake(exchange.connection()!!.handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build()
      code = response.code
    }

    exchange.responseHeadersEnd(response)

    response = if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response.newBuilder()
          .body(EMPTY_RESPONSE)
          .build()
    } else {
      response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build()
    }
    if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
        "close".equals(response.header("Connection"), ignoreCase = true)) {
      exchange.noNewExchangesOnConnection()
    }
    if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) {
      throw ProtocolException(
          "HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}")
    }
    return response
  }
}

6.1 大容量请求体处理

当我们使用大容量请求体的时候,会在请求头中添加字段:Expect :100-continue,此时会先把请求头发送出去,那么此时如果服务端返回了100的响应,

kotlin 复制代码
override fun readResponseHeaders(expectContinue: Boolean): Response.Builder? {
  val headers = stream!!.takeHeaders()
  val responseBuilder = readHttp2HeadersList(headers, protocol)
  // 如果响应头code 为100,那么返回null
  return if (expectContinue && responseBuilder.code == HTTP_CONTINUE) {
    null
  } else {
    responseBuilder
  }
}

那么就继续将请求体发送过去即可;如果服务端不允许,那么就直接返回了响应体,那么就把响应体当做response返回给上层即可。

总结

以上就是OkHttp的5大拦截器,RetryAndFollwupInterceptor作为护城河,帮助我们try-catch请求过程中的异常,并发起重试,保证请求的可靠性和稳定性,同时对于重定向的请求,会在内部处理并重新请求;BridgeInterceptor会帮助我们填补遗漏的请求头参数;CacheInterceptor会帮助缓存Http GET请求响应数据,并提供了详细的缓存策略告知我们是否可以使用缓存,还是需要重新从服务端发起请求,一定程度上提高了响应速度;ConnectInterceptor则是利用了OkHttp中的连接池,与目标服务器建立了socket连接,在可复用的基础上提高了请求的效率;CallServerInterceptor是责任链的最后一环,将请求发送到服务端,拿到服务器的响应,同时处理了大请求体的请求策略。

相关推荐
花生糖@11 分钟前
Android XR 应用程序开发 | 从 Unity 6 开发准备到应用程序构建的步骤
android·unity·xr·android xr
是程序喵呀14 分钟前
MySQL备份
android·mysql·adb
casual_clover16 分钟前
Android 之 List 简述
android·list
轩辰~31 分钟前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
锋风Fengfeng2 小时前
安卓15预置第三方apk时签名报错问题解决
android
User_undefined2 小时前
uniapp Native.js原生arr插件服务发送广播到uniapp页面中
android·javascript·uni-app
程序员厉飞雨3 小时前
Android R8 耗时优化
android·java·前端
EasyDSS4 小时前
国标GB28181-2022平台EasyGBS:安防监控中P2P的穿透方法
网络协议·php·音视频·p2p
网安墨雨4 小时前
常用网络协议
网络·网络协议
丘狸尾5 小时前
[cisco 模拟器] ftp服务器配置
android·运维·服务器