OkHttp3笔记---ConnectInterceptor

    ConnectInterceptor的作用为建立客户端和服务端的Http连接,其中连接(RealConnection)是可以复用的(针对Http2.0协议)。获取Http连接的过程如下:首先,检查当前的RealCall是否含有可用的Http连接,若包含,则返回可以的连接。否则,检查连接池是否含有可用的连接,若有,则返回。若依然找不到可用的连接,则建立一个新的Http连接,并将其保存到连接池中。

    Http连接的建立过程大体如下:

针对Http1.1:首先,根据服务端地址创建socket,然后,使用socket连接服务端,并保存socket

针对Http2.0:首先,创建socket,并连接到服务器,然后,将socket封装为sslSocket(为了将数据加密传输),使用SSL/TLS协议与服务端握手(确定传输密钥)。最后,封装sslSocket的输入流和输出流,用于读取Http2.0通信过程中的数据帧。Http2.0协议的相关连接

    接下来,主要针对Http2.0分析连接的建立过程。因为现在用的大多数请求都为Https协议下的Http2.0。

代码分析:

object ConnectInterceptor : Interceptor {
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    //建立Http连接,并获取exchange对象,其包含Http连接,用于管理Http连接
    val exchange = realChain.call.initExchange(chain)
    val connectedChain = realChain.copy(exchange = exchange)
    return connectedChain.proceed(realChain.request)
  }
}

    可以看到这里会调用initExchange方法去获取一个Exchange对象,此对象是用来管理Http连接的,如发送请求等I/O控制。initExchange方法的代码如下:

internal fun initExchange(chain: RealInterceptorChain): Exchange {
    ......
    val exchangeFinder = this.exchangeFinder!!
    val codec = exchangeFinder.find(client, chain)
    val result = Exchange(this, eventListener, exchangeFinder, codec)
    this.interceptorScopedExchange = result
    this.exchange = result
    ......
    return result
  }

    在此方法中,首先,调用exchangeFinder的find方法获取一个ExchangeCodec对象,此对象包含了一个Http连接,用来编码Http请求和解码Http响应。然后,将此对象封装到一个Exchange对象中。最后,将Exchange对象返回。find方法代码如下:

fun find(
    client: OkHttpClient,
    chain: RealInterceptorChain
  ): ExchangeCodec {
    try {
      val resultConnection = findHealthyConnection(
          connectTimeout = chain.connectTimeoutMillis,
          readTimeout = chain.readTimeoutMillis,
          writeTimeout = chain.writeTimeoutMillis,
          pingIntervalMillis = client.pingIntervalMillis,
          connectionRetryEnabled = client.retryOnConnectionFailure,
          doExtensiveHealthChecks = chain.request.method != "GET"
      )
      return resultConnection.newCodec(client, chain)
    } catch (e: RouteException) {
      trackFailure(e.lastConnectException)
      throw e
    } catch (e: IOException) {
      trackFailure(e)
      throw RouteException(e)
    }
  }
......
internal fun newCodec(client: OkHttpClient, chain: RealInterceptorChain): ExchangeCodec {
    val socket = this.socket!!
    val source = this.source!!
    val sink = this.sink!!
    //Http2.0的连接
    val http2Connection = this.http2Connection
    //将Http连接封装到codec中
    return if (http2Connection != null) {
      //针对Http2.0生成codec
      Http2ExchangeCodec(client, this, chain, http2Connection)
    } else {
      socket.soTimeout = chain.readTimeoutMillis()
      source.timeout().timeout(chain.readTimeoutMillis.toLong(), MILLISECONDS)
      sink.timeout().timeout(chain.writeTimeoutMillis.toLong(), MILLISECONDS)
      //针对Http1.0/1.1生成codec
      Http1ExchangeCodec(client, this, source, sink)
    }
  }

    可以看出find的方法,首先会调用findHealthyConnection获取一个Http连接resultConnection,此连接的类型为RealConnection,RealConnection内部包含了2中连接,分别为Http1.0/1.1(对应Http1Connection)和Http2.0(对应Http2Connenction),根据不同的Http请求,建立不同的Http连接。然后,调用RealConnection的newCodec方法创建一个ExchangeCodec,即将Http连接封装到一个codec对象中。最后,返回codec对象,以便使用此codec对象进行网络访问。

    findHealthyConnection方法会根据Http请求,建立一个Http连接,此部分即为ConnectInteerptor拦截器的核心工作,方法的代码如下:

private fun findHealthyConnection(
    connectTimeout: Int,
    readTimeout: Int,
    writeTimeout: Int,
    pingIntervalMillis: Int,
    connectionRetryEnabled: Boolean,
    doExtensiveHealthChecks: Boolean
  ): RealConnection {
    while (true) {
      //获取Http连接
      val candidate = findConnection(
          connectTimeout = connectTimeout,
          readTimeout = readTimeout,
          writeTimeout = writeTimeout,
          pingIntervalMillis = pingIntervalMillis,
          connectionRetryEnabled = connectionRetryEnabled
      )

      // 是可用连接,则将其返回
      if (candidate.isHealthy(doExtensiveHealthChecks)) {
        return candidate
      }

      //不是可用连接,检查路由,继续搜索可以连接
      ......
    }
  }

     此代码会调用findConnection方法,代码如下:

private fun findConnection(
    connectTimeout: Int,
    readTimeout: Int,
    writeTimeout: Int,
    pingIntervalMillis: Int,
    connectionRetryEnabled: Boolean
  ): RealConnection {

    // 1、获取当前call中可用的连接并返回
    val callConnection = call.connection // This may be mutated by releaseConnectionNoEvents()!
    if (callConnection != null) {
      var toClose: Socket? = null
      synchronized(callConnection) {
        if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
          toClose = call.releaseConnectionNoEvents()
        }
      }

      // 返回call中可用的连接
      if (call.connection != null) {
        check(toClose == null)
        return callConnection
      }
      ......
    }
    ......
    // 2、根据服务端的地址address,在连接池中,搜索一个包含相同地址的http连接
    if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
      val result = call.connection!!
      eventListener.connectionAcquired(call, result)
      return result
    }

    // 没找到有相同address的Http连接,接下来放宽要求,通过路由来找
    // 配置路由
    val routes: List?
    val route: Route
    if (nextRouteToTry != null) {
      // Use a route from a preceding coalesced connection.
      routes = null
      route = nextRouteToTry!!
      nextRouteToTry = null
    } else if (routeSelection != null && routeSelection!!.hasNext()) {
      // Use a route from an existing route selection.
      routes = null
      route = routeSelection!!.next()
    } else {
      // Compute a new route selection. This is a blocking operation!
      var localRouteSelector = routeSelector
      if (localRouteSelector == null) {
        localRouteSelector = RouteSelector(address, call.client.routeDatabase, call, eventListener)
        this.routeSelector = localRouteSelector
      }
      val localRouteSelection = localRouteSelector.next()
      routeSelection = localRouteSelection
      routes = localRouteSelection.routes

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

      // 3、根据路由从连接池,寻找可用连接,若2个连接的address不同,但是是连接的同一公司的服务器(如square.com和square.ca),则返回找到的连接
      if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
        val result = call.connection!!
        eventListener.connectionAcquired(call, result)
        return result
      }

      route = localRouteSelection.next()
    }

    // 4、在连接池中未找打可用连接,则建立一个新的Http连接
    val newConnection = RealConnection(connectionPool, route)
    call.connectionToCancel = newConnection
    try {
      newConnection.connect(
          connectTimeout,
          readTimeout,
          writeTimeout,
          pingIntervalMillis,
          connectionRetryEnabled,
          call,
          eventListener
      )
    } finally {
      call.connectionToCancel = null
    }
    call.client.routeDatabase.connected(newConnection.route())

    // 5、可能有其他线程建立了相同的Http连接(同时进行Http请求),释放当前的连接,复用已建立的Http连接。
    if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
      val result = call.connection!!
      nextRouteToTry = route
      newConnection.socket().closeQuietly()
      eventListener.connectionAcquired(call, result)
      return result
    }
    // 6、将连接保存到连接池
    synchronized(newConnection) {
      connectionPool.put(newConnection)
      call.acquireConnectionNoEvents(newConnection)
    }

    eventListener.connectionAcquired(call, newConnection)
    return newConnection
  }

    此方法会返回一个可用Http连接,获取Http连接的优先顺序,call中存在的连接>连接池中的连接>新建连接。connectionPool的callAcquirePolledConnection方法会检查连接的地址是否相同,是否达到最大连接数,是否有重合的路由等因素来决定是否对连接进行复用。在第4步,创建一个RealConnection后,需要调用其connect方法,以连接到服务端。接下来,以Https/2.0的连接方式进行分析。connect方法代码如下:

fun connect(
    connectTimeout: Int,
    readTimeout: Int,
    writeTimeout: Int,
    pingIntervalMillis: Int,
    connectionRetryEnabled: Boolean,
    call: Call,
    eventListener: EventListener
  ) {
    //Http连接前的检查
    ......

    while (true) {
      try {
        if (route.requiresTunnel()) {
          //通过通道连接,依然会调用connectSocket方法
          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener)
          if (rawSocket == null) {
            // We were unable to connect the tunnel but properly closed down our resources.
            break
          }
        } else {
          //建立TCP连接,会使用地址创建一个socket,然后使用socket连接服务器
          connectSocket(connectTimeout, readTimeout, call, eventListener)
        }
        //基于socket建立Http1.1/2.0连接
        establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)
        eventListener.connectEnd(call, route.socketAddress, route.proxy, protocol)
        break
      } catch (e: IOException) {
        //错误处理
        ......
      }
    }
    ......
  }

    此方法首先会建立一个socket连接(TCP连接),然后,调用establishProtocol方法建立Http1.1/2.0连接,代码如下:

private fun establishProtocol(
    connectionSpecSelector: ConnectionSpecSelector,
    pingIntervalMillis: Int,
    call: Call,
    eventListener: EventListener
  ) {
    //一般http请求sslSocketFactory为null,https请求不为null
    if (route.address.sslSocketFactory == null) {
      if (Protocol.H2_PRIOR_KNOWLEDGE in route.address.protocols) {
        socket = rawSocket
        protocol = Protocol.H2_PRIOR_KNOWLEDGE
        startHttp2(pingIntervalMillis)
        return
      }
      //一般的http请求会执行到此步
      socket = rawSocket
      protocol = Protocol.HTTP_1_1
      return
    }
    //https请求会执行以下步骤
    eventListener.secureConnectStart(call)
    //基于socket,进行TLS握手
    connectTls(connectionSpecSelector)
    eventListener.secureConnectEnd(call, handshake)

    if (protocol === Protocol.HTTP_2) {
      //建立http2.0连接
      startHttp2(pingIntervalMillis)
    }
  }

    我们主要分析Http2.0连接的建立过程,此过程中,首先,会调用connectTls方法进行TLS握手,确定密钥协议。然后,调用startHttp2方法建立Http2.0连接,依据Http2.0协议,对socket的输入和输出进行封装。startHttp2方法的代码如下:

private fun startHttp2(pingIntervalMillis: Int) {
    val socket = this.socket!!
    val source = this.source!!
    val sink = this.sink!!
    socket.soTimeout = 0 // HTTP/2 connection timeouts are set per-stream.
    val http2Connection = Http2Connection.Builder(client = true, taskRunner = TaskRunner.INSTANCE)
        .socket(socket, route.address.url.host, source, sink)
        .listener(this)
        .pingIntervalMillis(pingIntervalMillis)
        .build()
    this.http2Connection = http2Connection
    this.allocationLimit = Http2Connection.DEFAULT_SETTINGS.getMaxConcurrentStreams()
    //开始与服务端通信,读取数据
    http2Connection.start()
  }

    在此方法中,首先会新建一个Http2Connection对象,此对象代表了一个Http2.0连接,然后,调用start方法,开始与服务端进行通信。start方法的代码如下:

fun start(sendConnectionPreface: Boolean = true, taskRunner: TaskRunner = TaskRunner.INSTANCE) {
    if (sendConnectionPreface) {
      writer.connectionPreface()
      writer.settings(okHttpSettings)
      val windowSize = okHttpSettings.initialWindowSize
      if (windowSize != DEFAULT_INITIAL_WINDOW_SIZE) {
        //确定Http的窗口大小,每次返送数据,数据大小不能超过窗口大小
        writer.windowUpdate(0, (windowSize - DEFAULT_INITIAL_WINDOW_SIZE).toLong())
      }
    }
    // Thread doesn't use client Dispatcher, since it is scoped potentially across clients via
    // ConnectionPool.
    //开启一个读取数据帧的线程
    taskRunner.newQueue().execute(name = connectionName, block = readerRunnable)
  }

    在此方法会与服务器协定窗口的大小,并基于TaskRunner提交了一个循环读取服务端的数据帧的线程readRunnable ,其代码如下:

// 使用Http2Reader对socket的输入流进行封装,以读取Http2.0的数据帧
  val readerRunnable = ReaderRunnable(Http2Reader(builder.source, client))


inner class ReaderRunnable internal constructor(
    internal val reader: Http2Reader
  ) : Http2Reader.Handler, () -> Unit {
    override fun invoke() {
      var connectionErrorCode = ErrorCode.INTERNAL_ERROR
      var streamErrorCode = ErrorCode.INTERNAL_ERROR
      var errorException: IOException? = null
      try {
        reader.readConnectionPreface(this)
        //循环读取服务器发送的数据帧
        while (reader.nextFrame(false, this)) {
        }
        connectionErrorCode = ErrorCode.NO_ERROR
        streamErrorCode = ErrorCode.CANCEL
      } catch (e: IOException) {
        errorException = e
        connectionErrorCode = ErrorCode.PROTOCOL_ERROR
        streamErrorCode = ErrorCode.PROTOCOL_ERROR
      } finally {
        close(connectionErrorCode, streamErrorCode, errorException)
        reader.closeQuietly()
      }
    }
    //Http2Reader接口的具体方法实现
    ......

    ReaderRunnable会执行在一个线程中,并循环读取服务端发送的数据帧,读取数据帧的具体过程在此篇播客中讲解CallServerInterceptor。每个数据帧都有一个ID来表示其归属于哪一个Http请求。与Http2Reader对应的是Http2Writer,此类对socket输出流进行封装,用来发送Http2.0的数据帧。

   至此,Http连接就已经建立完成,接下来就可以进行发送数据和读取数据。

    总结,ConnectInterceptor用来建立Http连接,对于Https2.0来说,Http2Connection就代表了一个Http2.0连接,其内部的Http2Reader和Http2Writer分别对与服务器连接的socket的输入流和输出流进行了封装,以使其可以读取Http2.0通信过程中的数据帧。

你可能感兴趣的:(okHttp,android)