OKhttp拦截器详解2— ConnectInterceptor、CallServerInterceptor

前言

上一篇文章我们分析了OKhttp中前三个拦截器:RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor,它们主要是在请求建立前做一些预处理。如果请求经过这个三个拦截器后,还继续往下传递,说明是需要进行网络请求的(缓存无法满足),那么就会来到我们今天所有分析的两个拦截器:ConnectInterceptor(负责建立连接)、CallServerInterceptor(读写请求服务)。

ConnectInterceptor(连接拦截器)

连接拦截器ConnectInterceptor代码如下:

/*ConnectInterceptor*/
object ConnectInterceptor : Interceptor {
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.call.initExchange(realChain)
    val connectedChain = realChain.copy(exchange = exchange)
    return connectedChain.proceed(realChain.request)
  }
}

可以看到intercept方法中只有4行代码,其中关键代码只有一句

val exchange :ExChange = realChain.call.initExchange(realChain)

realChain.call获得一个ExChange对象, realChain.call其实就是RealCall 。下面我们跟到initExchange方法看一下:

 /*RealCall*/
internal fun initExchange(chain: RealInterceptorChain): Exchange {
    synchronized(this) {
      check(expectMoreExchanges) { "released" }
      check(!responseBodyOpen)
      check(!requestBodyOpen)
    }
    val exchangeFinder = this.exchangeFinder!!
    val connection : RealConnection = exchangeFinder.find()
    val codec : ExchangeCodec = connection.newCodec(client, chain)
    val result = Exchange(this, eventListener, exchangeFinder, codec)
    this.interceptorScopedExchange = result
    this.exchange = result
    synchronized(this) {
      this.requestBodyOpen = true
      this.responseBodyOpen = true
    }
    if (canceled) throw IOException("Canceled")
    return result
  }

从上面这段代码可以看到exchangeFinder通过find()方法获取到了RealConnection,然后RealConnection通过newCodec() 方法获取到ExchangeCodec实例,ExchangeCodec再作为参数构建了Exchange实例返回。这里稍微来认识下涉及到的几个类:

  • RealConnection :能够承载 1 个或多个并发流的远程 Web 服务器连接。
  • ExchangeCodec:接口类,负责真正的IO操作—写请求、读响应,实现类有Http1ExchangeCodecHttp2ExchangeCodec,分别对应HTTP1.1协议、HTTP2.0协议。
  • Exchange:管理IO操作,是ExchangeCodec的包装,增加了事件回调;一个请求对应一个Exchange实例。传给下个拦截器CallServerInterceptor使用。
  • ExchangeFinder:接口类,(从连接池中)寻找可用TCP连接(RealConnection),然后通过连接得到ExchangeCodec,实现类有FastFallbackExchangeFinderSequentialExchangeFinder(默认)

接下来我们就来看一下exchangeFinder.find()connection.newCodec()具体做了什么?

ExchangeFinder

ExchangeFinder的作用从名字就可以看出——Exchange寻找者,本质是为请求寻找一个TCP连接。上面我们已经知道它是一个接口类,有两个实现类FastFallbackExchangeFinderSequentialExchangeFinder。这两个实现类我们等会再来看具体内部是如何实现的。

先来看下ExchangeFinder初始化的地方(实际是实现类的初始化):

fun enterNetworkInterceptorExchange(
    request: Request,
    newRoutePlanner: Boolean,
    chain: RealInterceptorChain,
  ) {
  //前方省略一波代码
        ···
    if (newRoutePlanner) {
      val routePlanner = RealRoutePlanner(
        client,
        createAddress(request.url),
        this,
        chain,
        connectionListener = connectionPool.connectionListener
      )
      this.exchangeFinder = when {
        client.fastFallback -> FastFallbackExchangeFinder(routePlanner, client.taskRunner)
        else -> SequentialExchangeFinder(routePlanner)
      }
    }
  }

这个方法是在RetryAndFollowUpInterceptor中被调用的,为连接做准备。这里就可以看着ExchangeFinder的两个实现类的初始化,而且还看到都被传了个一个参数RealRoutePlanner,剧透一下,寻找连接RealConnection的主要判断逻辑都在这个类里面,这个我们稍后再说。这里我们先来看下createAddress()方法:

private fun createAddress(url: HttpUrl): Address {
    var sslSocketFactory: SSLSocketFactory? = null
    var hostnameVerifier: HostnameVerifier? = null
    var certificatePinner: CertificatePinner? = null
    if (url.isHttps) {
      sslSocketFactory = client.sslSocketFactory
      hostnameVerifier = client.hostnameVerifier
      certificatePinner = client.certificatePinner
    }
    return Address(
      uriHost = url.host,
      uriPort = url.port,
      dns = client.dns,
      socketFactory = client.socketFactory,
      sslSocketFactory = sslSocketFactory,
      hostnameVerifier = hostnameVerifier,
      certificatePinner = certificatePinner,
      proxyAuthenticator = client.proxyAuthenticator,
      proxy = client.proxy,
      protocols = client.protocols,
      connectionSpecs = client.connectionSpecs,
      proxySelector = client.proxySelector
    )
  }

使用urlclient配置创建一个Address实例。Address意思是指向服务的连接的地址,可以理解为请求地址及其配置。Address有一个重要作用:相同Address的HTTP请求 共享 相同的连接。这可以作为 HTTP1.1和HTTP2.0 复用连接 的请求的判断。

我们再来看下ExchangerFinder的两个实现类FastFallbackExchangeFinderSequentialExchangeFinder是如何实现find()的:
先看FastFallbackExchangeFinder

/*FastFallbackExchangeFinder*/
  override fun find(): RealConnection {
    var firstException: IOException? = null
    try {
      while (tcpConnectsInFlight.isNotEmpty() || routePlanner.hasNext()) {
        if (routePlanner.isCanceled()) throw IOException("Canceled")
        
        //如果已准备好就启动一个新连接
        val now = taskRunner.backend.nanoTime()
        var awaitTimeoutNanos = nextTcpConnectAtNanos - now
        var connectResult: ConnectResult? = null
        if (tcpConnectsInFlight.isEmpty() || awaitTimeoutNanos <= 0) {
          connectResult = launchTcpConnect()
          nextTcpConnectAtNanos = now + connectDelayNanos
          awaitTimeoutNanos = connectDelayNanos
        }
        // 等待正在进行的连接完成或失败
        if (connectResult == null) {
          connectResult = awaitTcpConnect(awaitTimeoutNanos, TimeUnit.NANOSECONDS) ?: continue
        }
        if (connectResult.isSuccess) {
      
          //已得到一个连接TCP成功的连接,取消和延迟所有丢失的竞争连接
          cancelInFlightConnects()
          
          // 如果是全新的连接需要走到这一步,进行TLS判断连接,如果是连接池中得到的连接则不用
          if (!connectResult.plan.isReady) {
            connectResult = connectResult.plan.connectTlsEtc()
          }

          if (connectResult.isSuccess) {
            //在这里加入连接池
            return connectResult.plan.handleSuccess()
          }
        }
        val throwable = connectResult.throwable
        if (throwable != null) {
          if (throwable !is IOException) throw throwable
          if (firstException == null) {
            firstException = throwable
          } else {
            firstException.addSuppressed(throwable)
          }
        }
        val nextPlan = connectResult.nextPlan
        if (nextPlan != null) 
        
          //将下一个从竞争中胜出的连接加入到集合中(deferredPlans),下次获取连接时会进行尝试
          routePlanner.deferredPlans.addFirst(nextPlan)
        }
      }
    } finally {
      cancelInFlightConnects()
    }
    throw firstException!!
  }

再来看下SequentialExchangeFinder

/*SequentialExchangeFinder*/
override fun find(): RealConnection {
    var firstException: IOException? = null
    while (true) {
      if (routePlanner.isCanceled()) throw IOException("Canceled")

      try {
        val plan = routePlanner.plan()

        if (!plan.isReady) {
          val tcpConnectResult = plan.connectTcp()
          val connectResult = when {
            tcpConnectResult.isSuccess -> plan.connectTlsEtc()
            else -> tcpConnectResult
          }
          val (_, nextPlan, failure) = connectResult

          if (failure != null) throw failure
          if (nextPlan != null) {
            routePlanner.deferredPlans.addFirst(nextPlan)
            continue
          }
        }
        return plan.handleSuccess()
      } catch (e: IOException) {
        if (firstException == null) {
          firstException = e
        } else {
          firstException.addSuppressed(e)
        }
        if (!routePlanner.hasNext()) {
          throw firstException
        }
      }
    }
  }

说一下这两个实现类的区别:两者尝试获取连接的策略不同,FastFallbackExchangeFinder的策略每隔250ms异步启动一个新的尝试(连接到目标地址会有多个IP),只要其中一个IP返回连接成功就返回;SequentialExchangeFinder则是尝试一次路由一个,直到连接成功。所以明显FastFallbackExchangeFinder效率更高,也是OKhttp的默认的方式。

ExchangFinderfind()方法获取的RealConnection后,RealConnection又是如何通过newCodec()获取到ExchangeCodec实例?我们跟进去看下:

/*RealConnection*/
@Throws(SocketException::class)
  internal fun newCodec(client: OkHttpClient, chain: RealInterceptorChain): ExchangeCodec {
    val socket = this.socket!!
    val source = this.source!!
    val sink = this.sink!!
    val http2Connection = this.http2Connection

    return if (http2Connection != null) {
      Http2ExchangeCodec(client, this, chain, http2Connection)
    } else {
      socket.soTimeout = chain.readTimeoutMillis()
      source.timeout().timeout(chain.readTimeoutMillis.toLong(), MILLISECONDS)
      sink.timeout().timeout(chain.writeTimeoutMillis.toLong(), MILLISECONDS)
      Http1ExchangeCodec(client, this, source, sink)
    }
  }

代码很简单,判断了是http1还是http2,根据判断new出不同的实例并返回。

到这里,ConnectInterceptorintercept中的大致流程算是梳理通顺了,但还有些细节需要弄清楚:

  1. 连接如何拿到的?
  2. 连接是如何复用的?
  3. DNS和Socket又是如何连接的?

如何得到连接

看前面FastFallbackExchangeFinderFastFallbackExchangeFinder两个实现类中的find(),一路看下去都能看到这么一句代码:

 routePlanner.plan()

routePlanner是一个RoutePlanner类型实例,RoutePlanner是个接口类,实现类其实是RealRoutePlanner,我们跟到plan()方法中看一下:

/*RealRoutePlanner*/ 
@Throws(IOException::class)
 override fun plan(): Plan {
   //1.返回已分配的连接
   val reuseCallConnection = planReuseCallConnection()
   if (reuseCallConnection != null) return reuseCallConnection

   // 2.第一次尝试从连接池中获取连接
   val pooled1 = planReusePooledConnection()
   if (pooled1 != null) return pooled1

   // 3.在新routes前尝试之前推迟连接
   if (deferredPlans.isNotEmpty()) return deferredPlans.removeFirst()

   // 4.通过routeSelector来获取到新的Route来进行Connection的建立
   val connect = planConnect()

   // 5.第二次尝试连接池中获取连接,如果获取到直接返回
   val pooled2 = planReusePooledConnection(connect, connect.routes)
   if (pooled2 != null) return pooled2
   return connect
 }

从上面流程可以看到,plan()方法做了以下几件事:

  1. 检查已分配过的Connection是否满足此次请求,如果满足直接返回。
  2. 检查当前连接池ConnectionPool中是否有满足此次请求的Connection。
  3. 在使用Route前,检查推迟连接(之前连接竞争中胜出但是未使用的连接)集合中是否有可用连接,尝试使用。
  4. 检查当前RouteSelector列表中,是否还有可用Route(Route是proxy,IP地址的包装类), 如果没有就发起DNS请求。
  5. 通过DNS获取到新的Route之后,第二次从ConnectionPool查找有无可复用的Connection,否则就创建新的RealConnection

可以看到plan()返回的是一个Plan类型,这是一个接口类,实际返回的是它的实现类ConnectPlan,`ConnectPlan官方解释是:单次连接服务器的尝试。它包含的步骤有:

  1. TCP握手
  2. [可选] 连接隧道(使用 HTTP 代理访问 HTTPS 服务器时我们必须发送“CONNECT”请求,并处理来自代理的授权质询)
  3. [可选] TLS握手

每个步骤都可能失败。如果可以重试,则会使用下一个Plan创建一个新实例,该将采用不同的配置。

回过头我们再来到FastFallbackExchangeFinderFastFallbackExchangeFinder类似),当拿到表示连接的 Plan后会被包装成一个ConnectResult,这就是个data类:

 /*FastFallbackExchangeFinder*/
private fun launchTcpConnect(): ConnectResult? {
    val plan = when {
      routePlanner.hasNext() -> {
        try {
          routePlanner.plan()
        } catch (e: Throwable) {
          FailedPlan(e)
        }
      }
      else -> return null // Nothing further to try.
    }
    // Already connected. Return it immediately.
    if (plan.isReady) return ConnectResult(plan)

    // Already failed? Return it immediately.
    if (plan is FailedPlan) return plan.result

    // Connect TCP asynchronously.
    tcpConnectsInFlight += plan
    val taskName = "$okHttpName connect ${routePlanner.address.url.redact()}"
    taskRunner.newQueue().schedule(object : Task(taskName) {
      override fun runOnce(): Long {
        val connectResult = try {
          plan.connectTcp()
        } catch (e: Throwable) {
          ConnectResult(plan, throwable = e)
        }
        // Only post a result if this hasn't since been canceled.
        if (plan in tcpConnectsInFlight) {
          connectResults.put(connectResult)
        }
        return -1L
      }
    })
    return null
  }

接着就将ConnectResult返回到find()中,如果你还记得上面FastFallbackExchangeFinderfind()方法的话,里面有这么一段代码:

/*FastFallbackExchangeFinder*/
  override fun find(): RealConnection {
   ...
    try {
      while (tcpConnectsInFlight.isNotEmpty() || routePlanner.hasNext()) {
     ...
        // 等待正在进行的连接完成或失败
        if (connectResult == null) {
          connectResult = awaitTcpConnect(awaitTimeoutNanos, TimeUnit.NANOSECONDS) ?: continue
        }
        if (connectResult.isSuccess) {
      
          //已得到一个连接TCP成功的连接,取消和延迟所有丢失的竞争连接
          cancelInFlightConnects()
          
          // 如果是全新的连接需要走到这一步,进行TLS判断连接,如果是连接池中得到的连接则不用
          if (!connectResult.plan.isReady) {
            connectResult = connectResult.plan.connectTlsEtc()
          }

          if (connectResult.isSuccess) {
            //在这里加入连接池
            return connectResult.plan.handleSuccess()
          }
        }
        val throwable = connectResult.throwable
        if (throwable != null) {
      ···
        if (nextPlan != null) 
          //将下一个从竞争中胜出的连接加入到集合中(deferredPlans),下次获取连接时会进行尝试(上面获取连接时的第三步判断)
          routePlanner.deferredPlans.addFirst(nextPlan)
        ···
      }
    } finally {
      cancelInFlightConnects()
    }
  ···
  }

经过一系列连接、判断操作,最终执行到connectResult.plan.handleSuccess(),表示连接成功,并且将新建的连接放到连接池。

/*ConnectPlan*/
override fun handleSuccess(): RealConnection {
   ···
  //如果连接池中匹配到相同连接,则合并连接。
  val pooled3 = routePlanner.planReusePooledConnection(this, routes)
    if (pooled3 != null) return pooled3.connection
  //连接池中未匹配到相同连接,将该连接加入连接池
    synchronized(connection) {
      client.connectionPool.delegate.put(connection)
      call.acquireConnectionNoEvents(connection)
    }
  ···
    return connection
  }

连接复用

从上面的获取连接的步骤可以看到,第二步和第五步对ConnectionPool做了两次复用检查,且拿到Plan后当连接成功将新建的连接放入到ConnectionPool中。

因此这里就是OkHttp的连接复用其实是通过ConnectionPool来实现的,我们追溯ConnectionPool代码会发现它内部拥有个代理对象delegate,如同它的名字RealConnectionPool,这才是真正的连接池。

/*ConnectionPool*/
class ConnectionPool internal constructor(
  internal val delegate: RealConnectionPool
) 

继续追到里面会发现RealConnectionPool内部有一个connections的ConcurrentLinkedQueue对象就是用来保存缓存的连接。

/*RealConnectionPool*/
private val connections = ConcurrentLinkedQueue<RealConnection>()

DNS 过程

从前面分析的步骤可知,Dns的过程隐藏在了第四步RouteSelector检查中,其实也就是在 planConnect()中:

/*RealRoutePlanner*/
@Throws(IOException::class)
  private fun planConnect(): ConnectPlan {
    //使用来自前面合并连接的route
    val localNextRouteToTry = nextRouteToTry
    if (localNextRouteToTry != null) {
      nextRouteToTry = null
      return planConnectToRoute(localNextRouteToTry)
    }

    // 使用从现有route选项中获取的route
    val existingRouteSelection = routeSelection
    if (existingRouteSelection != null && existingRouteSelection.hasNext()) {
      return planConnectToRoute(existingRouteSelection.next())
    }

    // 确定要使用的代理(如果有)。这可能会在 ProxySelector.select() 中阻塞。
    var newRouteSelector = routeSelector
    if (newRouteSelector == null) {
      newRouteSelector = RouteSelector(
        address = address,
        routeDatabase = call.client.routeDatabase,
        call = call,
        fastFallback = client.fastFallback,
        eventListener = call.eventListener
      )
      routeSelector = newRouteSelector
    }

    // 列出当前代理的可用 IP 地址。这可能会在 Dns.lookup() 中阻塞。
    if (!newRouteSelector.hasNext()) throw IOException("exhausted all routes")
    val newRouteSelection = newRouteSelector.next()
    routeSelection = newRouteSelection

    if (call.isCanceled()) throw IOException("Canceled")

    return planConnectToRoute(newRouteSelection.next(), newRouteSelection.routes)
  }

不是很好理解,我们先搞明白RouteSelector, RouteSelectionRoute这三个类的关系,就比较容易理解了,下面给出三个类之间的关系图:

1711116c907b6d0b_tplv-t2oaga2asx-zoom-in-crop-mark_3024_0_0_0.jpg

根据上图结合源码梳理一下:

  • RouteSelector在调用next遍历在不同proxy情况下获得下一个Selection封装类,Selection持有一个Route的列表,也就是每个proxy都对应有Route列表。

  • Selection其实就是针对List封装的一个迭代器,通过next()方法获得下一个RouteRoute持有proxy、address和inetAddress,可以理解为Route就是针对IPProxy配对的一个封装。

  • RouteSelectornext()方法内部调用了nextProxy(), nextProxy()又会调用resetNextInetSocketAddres()方法。

  • resetNextInetSocketAddres通过address.dns.lookup获取InetSocketAddress,也就是IP地址。

通过上面的梳理我们知道,IP地址最终是通过address内的dns获取到的,而这个dns又是怎么构建的呢?

在分析ExchangeFinder时,大家还记得展示的createAddress()的代码嘛?不记得可以返回去看一下,从这段代码我们就能知道在创建address时,将内置的client.dns传递进来,而client.dns是在OkHttpclient的构建过程中传递进来Dns.System,里面的lookup是通过InetAddress.getAllByName 方法获取到对应域名的IP,也就是默认的Dns实现。

至此,整个DNS的过程就真相大白了。

建立Socket连接

通过Dns获得Connectoin之后,就是建立连接的过程了。此时我们又要回到FastFallbackExchangeFinder,如果还有印象,在分析如何得到连接的时候,有带着大家看过这么一个方法launchTcpConnect(),里面当拿到的是一个新连接的时候,会执行这么一段代码:

/*FastFallbackExchangeFinder*/    
// Connect TCP asynchronously.
private fun launchTcpConnect(): ConnectResult? {
  ···
    taskRunner.newQueue().schedule(object : Task(taskName) {
      override fun runOnce(): Long {
        ···
        //执行ConnectPlan内的connectTcp()进行TCP连接
          plan.connectTcp()
        ···
        return -1L
      }
    })
  return null
}

可以看到执行了 plan.connectTcp(),我们跟进去看一下:

/*ConnectPlan*/  
override fun connectTcp(): ConnectResult {
    check(rawSocket == null) { "TCP already connected" }
    var success = false
  //告知call有关连接call的信息,以便异步取消工作
    call.plansToCancel += this
    try {
      eventListener.connectStart(call, route.socketAddress, route.proxy)
      connectionListener.connectStart(route, call)
       //建立socket连接
      connectSocket()
      success = true
      return ConnectResult(plan = this)
    } catch (e: IOException) {
      eventListener.connectFailed(call, route.socketAddress, route.proxy, null, e)
      connectionListener.connectFailed(route, call, e)
      return ConnectResult(plan = this, throwable = e)
    } finally {
      call.plansToCancel -= this
      if (!success) {
        //清理资源
        rawSocket?.closeQuietly()
      }
    }
  }

connectTcp方法并不复杂,先做了简单的判断,调用了一些回调代码,然后调用系统方法建立Socket连接。

FastFallbackExchangeFinderfind()方法能看到,在connectTcp后,还会继续调用一个ConnectPlan中的另一个方法connectTlsEtc(),主要是如果请求是https请求,还需要一个TLS的连接建立。

CallServerInterceptor(请求服务拦截器)

CalllServerInterceptor是最后一个拦截器了,前面的拦截器已经完成了socket连接和tls连接,那么这一步就是传输http的头部和body数据了。

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!!//上个拦截器传入的exchange
    val request = realChain.request
    val requestBody = request.body
    val sentRequestMillis = System.currentTimeMillis()

    var invokeStartEvent = true
    var responseBuilder: Response.Builder? = null
    var sendRequestException: IOException? = null
    try {
      //写请求头
      exchange.writeRequestHeaders(request)
    //含body的请求
      if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
      // 若请求头包含 "Expect: 100-continue" , 就会等服务端返回含有 "HTTP/1.1 100 Continue"的响应,然后再发送请求body. 
      //如果没有收到这个响应(例如收到的响应是4xx),那就不发送body了。
        if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
          exchange.flushRequest()
          responseBuilder = exchange.readResponseHeaders(expectContinue = true)
          exchange.responseHeadersStart()
          invokeStartEvent = false
        }
         //responseBuilder为null说明服务端返回了100,也就是可以继续发送body了
        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 {
              // 满足了 "Expect: 100-continue" ,写请求body
            val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
            requestBody.writeTo(bufferedRequestBody)
            bufferedRequestBody.close()
          }
        } else {
           //没有满足 "Expect: 100-continue" ,请求发送结束
          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()
      }
    } catch (e: IOException) {
      if (e is ConnectionShutdownException) {
        throw e // No request was sent so there's no response to read.
      }
      if (!exchange.hasFailure) {
        throw e // Don't attempt to read the response; we failed to send the request.
      }
      sendRequestException = e
    }

    try {
      if (responseBuilder == null) {
        responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
        //回调 读响应头开始事件(如果上面没有)
        if (invokeStartEvent) {
          exchange.responseHeadersStart()
          invokeStartEvent = false
        }
      }
      
      //构建response
      var response = responseBuilder
          .request(request)
          .handshake(exchange.connection.handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build()
      var code = response.code
      
    //这里服务端又返回了个100,就再尝试获取真正的响应()
      if (shouldIgnoreAndWaitForRealResponse(code, exchange)) {
        responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
        if (invokeStartEvent) {
          exchange.responseHeadersStart()
        }
        response = responseBuilder
            .request(request)
            .handshake(exchange.connection.handshake())
            .sentRequestAtMillis(sentRequestMillis)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build()
        code = response.code
      }
   //回调读响应头结束
      exchange.responseHeadersEnd(response)
   //这里就是获取响应body了
      response = if (forWebSocket && code == 101) {
        // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
        response.stripBody()
      } else {
        response.newBuilder()
            .body(exchange.openResponseBody(response))
            .build()
      }
      
      //请求头中Connection是close,表示请求完成后要关闭连接
      if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
          "close".equals(response.header("Connection"), ignoreCase = true)) {
        exchange.noNewExchangesOnConnection()
      }
      //204(无内容)、205(充值内容),body应该是空
      if ((code == 204 || code == 205) && response.body.contentLength() > 0L) {
        throw ProtocolException(
            "HTTP $code had non-zero Content-Length: ${response.body.contentLength()}")
      }
      return response
    } catch (e: IOException) {
      if (sendRequestException != null) {
        sendRequestException.addSuppressed(e)
        throw sendRequestException
      }
      throw e
    }
  }

  private fun shouldIgnoreAndWaitForRealResponse(code: Int, exchange: Exchange): Boolean = when {
    // Server sent a 100-continue even though we did not request one. Try again to read the
    // actual response status.
    code == 100 -> true

    // Handle Processing (102) & Early Hints (103) and any new codes without failing
    // 100 and 101 are the exceptions with different meanings
    // But Early Hints not currently exposed
    code in (102 until 200) -> true

    else -> false
  }
}

整个内容就是前面说的一句话:写入http请求的header和body、读取响应的header和body。这里就不再解释了。

这里我们可以看到,无论写请求还是读响应,都是使用Exchange对应的方法。上面也提到过Exchange理解上是对ExchangeCodec的包装,这写方法内部除了事件回调和一些参数获取外,核心工作都由 ExchangeCodec 对象完成,而 ExchangeCodec实际上利用的是 Okio,而 Okio 实际上还是用的 Socket。

ExchangeCodec的实现类 有对应Http1.1的Http1ExchangeCodec 和 对应Http2.0的Http2ExchangeCodec。其中Http2ExchangeCodec是使用Http2.0中 数据帧 的概念完成请求响应的读写。关于Http1ExchangeCodecHttp2ExchangeCodec具体实现原理涉及okio这不再展开。

CallServerInterceptorintercept方法中没有调用连接器链Chainproceed方法,因为这是最后一个拦截器了。

总结

通过本篇的学习,我们知道了ConnectInterceptor负责连接的获取,其中涉及到连接池的概念;CallServerInterceptor是真正的网络IO读写。ConnectInterceptor涉及的内容较多,它是Okhttp的核心。结合上篇,我们我们已经分析完了Okhttp内部所有的拦截器。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容