联系我们
简单又实用的WordPress网站制作教学
当前位置:网站首页 > 程序开发学习 > 正文

Android开源框架系列-OkHttp4.11.0(kotlin版)- 拦截器分析之ConnectInterceptor

作者:访客发布时间:2023-12-24分类:程序开发学习浏览:83


导读:前言ConnectInterceptor顾名思义,连接拦截器,就是在发起连接之前进行拦截的拦截器,那么在连接之前拦截是要做什么?普通的连接做法肯定是直接创建一个新的连接就去连了,...

前言

ConnectInterceptor顾名思义,连接拦截器,就是在发起连接之前进行拦截的拦截器,那么在连接之前拦截是要做什么?普通的连接做法肯定是直接创建一个新的连接就去连了,但是okhttp不是这样,okhttp内部维护了一个连接池,连接池中存放的是一些使用完成的连接,在一定时间内达到复用的目的,以减少新建连接的消耗,类似于线程池。

intercept

我们直接进入它的intercept方法查看源码,可以看到整个ConnectInterceptor的源码也就这四行,但是不要掉以轻心,虽然看起来很短,但是其实隐含的逻辑很多。

@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
  //拿到责任链
  val realChain = chain as RealInterceptorChain
  //责任链上的call对象指的是发请求时创建的RealCall对象,调用它的initExchange方法
  //得到的是Exchange对象,这个对象是做什么的,见1
  val exchange = realChain.call.initExchange(chain)
  val connectedChain = realChain.copy(exchange = exchange)
  //继续将任务放到链上去执行下一个拦截器
  return connectedChain.proceed(realChain.request)
}

initExchange

接下来步入正题了,来看看initExchange方法。

internal fun initExchange(chain: RealInterceptorChain): Exchange {
  //看到这里,RealCall上有一个exchangeFinder对象,并且不为空,这个对象的创建时机是
  //在RetryAndFollowUpInterceptor中,还记得当时的enterNetworkInterceptorExchange
  //方法吗,见2
  val exchangeFinder = this.exchangeFinder!!
  //执行它的find方法得到的是一个ExchangeCodec对象,codec很明显,是一个编解码器
  //见3
  val codec = exchangeFinder.find(client, chain)
  //找到ExchangeCodec之后,就以其为参数,创建一个Exchange对象
  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")
  //最终返回这个Exchange对象
  return result
}

2 enterNetworkInterceptorExchange

一开始的时候ExchangeFinder就已经被创建了出来,ExchangeFinder顾名思义,就是负责找某信息的,所以它有一个find方法。通过它的构造函数可以知道,它持有连接池对象,而find的过程就是在连接池中find。另外就是ExchangeFinder持有了本次请求的url,这个也很关键。

fun enterNetworkInterceptorExchange(request: Request, newExchangeFinder: Boolean) {
  if (newExchangeFinder) {
    this.exchangeFinder = ExchangeFinder(
        connectionPool,
        createAddress(request.url),
        this,
        eventListener
    )
  }
}

3.find

find方法看似像是要查找一个ExchangeCodec对象,其实最终的ExchangeCodec是创建出来的,真正会被查找的是RealConnection,通过查找到RealConnection对象,来创建ExchangeCodec。

fun find(
  client: OkHttpClient,
  chain: RealInterceptorChain
): ExchangeCodec {
  try {
    //寻找一个健康的连接对象,见4
    val resultConnection = findHealthyConnection(
        connectTimeout = chain.connectTimeoutMillis,
        readTimeout = chain.readTimeoutMillis,
        writeTimeout = chain.writeTimeoutMillis,
        pingIntervalMillis = client.pingIntervalMillis,
        connectionRetryEnabled = client.retryOnConnectionFailure,
        doExtensiveHealthChecks = chain.request.method != "GET"
    )
    //通过这个连接对象创建一个编解码器,见5
    return resultConnection.newCodec(client, chain)
  } catch (e: RouteException) {
    trackFailure(e.lastConnectException)
    throw e
  } catch (e: IOException) {
    trackFailure(e)
    throw RouteException(e)
  }
}

4.findHealthyConnection

@Throws(IOException::class)
private fun findHealthyConnection(
  connectTimeout: Int,
  readTimeout: Int,
  writeTimeout: Int,
  pingIntervalMillis: Int,
  connectionRetryEnabled: Boolean,
  doExtensiveHealthChecks: Boolean
): RealConnection {
  //一个无限循环,不满足条件的话会一直找,直到找到
  while (true) {
    //又是一个find, 它的返回值是RealConnection,见6
    val candidate = findConnection(
        connectTimeout = connectTimeout,
        readTimeout = readTimeout,
        writeTimeout = writeTimeout,
        pingIntervalMillis = pingIntervalMillis,
        connectionRetryEnabled = connectionRetryEnabled
    )

    // 检查这个连接对象是否是健康的,如果是则直接返回使用
    if (candidate.isHealthy(doExtensiveHealthChecks)) {
      return candidate
    }

    // 如果不是健康的,将其noNewExchanges标记设置为true, 这样一来,就不会再使用这个
    //连接对象,并将其从连接池移除
    candidate.noNewExchanges()

    //如果还有其他线路可以尝试,则继续下一轮循环
    if (nextRouteToTry != null) continue
    //同样,如果还有其他线路,继续
    val routesLeft = routeSelection?.hasNext() ?: true
    if (routesLeft) continue
    //又是一个线路判断,有则继续尝试
    val routesSelectionLeft = routeSelector?.hasNext() ?: true
    if (routesSelectionLeft) continue
    //抛出异常
    throw IOException("exhausted all routes")
  }
}

5.newCodec

@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) {
    //说明当前是http2请求,构建一个Http2ExchangeCodec,codec很明显是个解码器
    Http2ExchangeCodec(client, this, chain, http2Connection)
  } else {
    //不是http2构建Http1ExchangeCodec
    socket.soTimeout = chain.readTimeoutMillis()
    source.timeout().timeout(chain.readTimeoutMillis.toLong(), MILLISECONDS)
    sink.timeout().timeout(chain.writeTimeoutMillis.toLong(), MILLISECONDS)
    Http1ExchangeCodec(client, this, source, sink)
  }
}

6.findConnection

findConnection方法很长,它的作用是返回一个RealConnection,这个RealConnection会优先使用已存在的连接,如果没有,则尝试从连接池中获取,连接池中获取不到就再创建新的连接。见注释。

@Throws(IOException::class)
private fun findConnection(
  connectTimeout: Int,
  readTimeout: Int,
  writeTimeout: Int,
  pingIntervalMillis: Int,
  connectionRetryEnabled: Boolean
): RealConnection {
  if (call.isCanceled()) throw IOException("Canceled")

  // 如果call上已经有一个连接了,条件满足则复用
  val callConnection = call.connection 
  if (callConnection != null) {
    var toClose: Socket? = null
    synchronized(callConnection) {
      //如果当前连接已经被标记为noNewExchanges,则不能使用(前边提到,不健康的连接会被标记)
      if (callConnection.noNewExchanges || 
      //host和port不同,也不能复用
      !sameHostAndPort(callConnection.route().address.url)) {
        //关闭连接,返回需要释放的socket对象,见7
        toClose = call.releaseConnectionNoEvents()
      }
    }

    // connection不为空,说明可以复用,直接返回这个connection即可
    if (call.connection != null) {
      check(toClose == null)
      return callConnection
    }

    // 走到这里说明没有复用成功,若toClose不为空,说明这个socket需要关闭掉
    toClose?.closeQuietly()
    eventListener.connectionReleased(call, callConnection)
  }

  refusedStreamCount = 0
  connectionShutdownCount = 0
  otherFailureCount = 0

  // 前边的连接不可用,只能尝试从连接池找一个能用的,见8
  if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
    //在连接池找到了可以复用的连接,直接返回时使用
    val result = call.connection!!
    eventListener.connectionAcquired(call, result)
    return result
  }

  // 走到这里表明连接池中没有可用的
  val routes: List<Route>?
  val route: Route
  // 记得findConnection方法是在一个循环中调用的,所以它可能会多次调用执行,
  //第一次执行的时候nextRouteToTry不会被赋值,所以一定是null
  if (nextRouteToTry != null) {
    routes = null
    route = nextRouteToTry!!
    nextRouteToTry = null
  } 
  //routeSelection第一次也一定是null
  else if (routeSelection != null && routeSelection!!.hasNext()) {
    routes = null
    route = routeSelection!!.next()
  } else {
    // 第一次执行routeSelector为空,所以会创建RouteSelector对象出来
    var localRouteSelector = routeSelector
    if (localRouteSelector == null) {
      //创建RouteSelector,RouteSelector内部有init方法,创建的时候会执行,见11.
      localRouteSelector = RouteSelector(address, call.client.routeDatabase, call, eventListener)
      //赋值,routeSelector不为空了
      this.routeSelector = localRouteSelector
    }
    //路线选择器,找到下一个路线,localRouteSelection为Selection对象,内部包含了一个
    //ip地址列表,见14
    val localRouteSelection = localRouteSelector.next()
    //给routeSelection赋值了,此时上边的else if才是满足的
    routeSelection = localRouteSelection
    //routes为所有ip的集合
    routes = localRouteSelection.routes

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

    // 再次尝试从连接池中匹配连接对象,由于是第一次请求,所以这里我们认为它还是空的,匹配不到
    // 见8,已经分析过此方法了,不再重复
    if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
      val result = call.connection!!
      eventListener.connectionAcquired(call, result)
      return result
    }
    //选一条ip进行尝试
    route = localRouteSelection.next()
  }

  // 没有可用连接,连接池中也没找到连接,只能创建新的连接
  val newConnection = RealConnection(connectionPool, route)
  call.connectionToCancel = newConnection
  try {
    //连接创建后调用connect方法开始连接,见18
    newConnection.connect(
        connectTimeout,
        readTimeout,
        writeTimeout,
        pingIntervalMillis,
        connectionRetryEnabled,
        call,
        eventListener
    )
  } finally {
    call.connectionToCancel = null
  }
  //连接成功,确保从routeDatabase中的集合中移除,这个集合记录的是连接失败的线路
  call.client.routeDatabase.connected(newConnection.route())

  // 又一次尝试从连接池获取连接,不同的是最后一个参数设置为true,
  if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
    val result = call.connection!!
    nextRouteToTry = route
    newConnection.socket().closeQuietly()
    eventListener.connectionAcquired(call, result)
    return result
  }
  //走到这里说明连接池没有相同连接,所以将本次创建的连接对象存入连接池
  synchronized(newConnection) {
    connectionPool.put(newConnection)
    call.acquireConnectionNoEvents(newConnection)
  }

  eventListener.connectionAcquired(call, newConnection)
  //返回这个连接
  return newConnection
}

7.releaseConnectionNoEvents

releaseConnectionNoEvents会从连接的分配列表中删除此调用,连接列表什么意思?每个connection对象都维护了一个calls集合,里边存储的是这个连接上所有的请求对象(RealCall),也就是多个请求复用一个连接对象。

internal fun releaseConnectionNoEvents(): Socket? {
  val connection = this.connection!!
  connection.assertThreadHoldsLock()
  //拿到这个连接对象的连接列表,列表里都是call,
  val calls = connection.calls
  //找到本次call对象在列表中的位置
  val index = calls.indexOfFirst { it.get() == this@RealCall }
  check(index != -1)
  //将本次call从列表移除,
  calls.removeAt(index)
  this.connection = null
  //再将当前call上的connection置空,这样一来,call和connection彻底断开
  if (calls.isEmpty()) {
    //移除之后列表空了,标明这个connection上当前只有一个call,唯一的请求被移除了
    connection.idleAtNs = System.nanoTime()
    //假如此时连接池已经进入了静止状态,那么连接的socket也要释放一下,返回这个socket
    if (connectionPool.connectionBecameIdle(connection)) {
      return connection.socket()
    }
  }
  //如果还有其他请求则不需要处理socket
  return null
}

8.callAcquirePooledConnection

callAcquirePooledConnection请求从连接池获取连接,注意,这次传入的第三个参数为null,第四个参数为false。

fun callAcquirePooledConnection(
  address: Address,
  call: RealCall,
  routes: List<Route>?,
  requireMultiplexed: Boolean
): Boolean {
  //遍历所有连接
  for (connection in connections) {
    synchronized(connection) {
      //false, 不会执行这条逻辑
      if (requireMultiplexed && !connection.isMultiplexed) return@synchronized
      //判断当前连接是否可用,见9
      if (!connection.isEligible(address, routes)) return@synchronized
      //连接可用,将conniption设置给请求对象,见10
      call.acquireConnectionNoEvents(connection)
      return true
    }
  }
  return false
}

9.isEligible

internal fun isEligible(address: Address, routes: List<Route>?): Boolean {
  assertThreadHoldsLock()

  // 每个connection可以被复用的次数有限,如果维护的请求已经超过限制,返回false
  if (calls.size >= allocationLimit || noNewExchanges) return false

  // 如果当前address的dns、protocols、port等等(除了host之外),这些信息必须和当前
  //connection的一致,否则不能复用
  if (!this.route.address.equalsNonHost(address)) return false

  // host也要相同,才能复用 
  if (address.url.host == this.route().address.url.host) {
    return true // This connection is a perfect match.
  }
  //http2支持多路复用,所以即使不满足以上条件,也是有机会复用连接的
  if (http2Connection == null) return false
  if (routes == null || !routeMatchesAny(routes)) return false
  if (address.hostnameVerifier !== OkHostnameVerifier) return false
  if (!supportsUrl(address.url)) return false
  try {
    //验证证书
    address.certificatePinner!!.check(address.url.host, handshake()!!.peerCertificates)
  } catch (_: SSLPeerUnverifiedException) {
    return false
  }
  //满足复用条件
  return true 
}

10.acquireConnectionNoEvents

fun acquireConnectionNoEvents(connection: RealConnection) {
  connection.assertThreadHoldsLock()

  check(this.connection == null)
  //给当前call的connection成员赋值
  this.connection = connection
  //并将当前call加入到这个connection的请求列表中
  connection.calls.add(CallReference(this, callStackTrace))
}

11.init

init {
  resetNextProxy(address.url, address.proxy)
}
private fun resetNextProxy(url: HttpUrl, proxy: Proxy?) {
  fun selectProxies(): List<Proxy> {
    // 如果调用方指定了代理,那么就只能使用代理,指定的方式见12
    if (proxy != null) return listOf(proxy)

    // 如果uri没有解析出host,认为它不可用,返回Proxy.NO_PROXY
    val uri = url.toUri()
    if (uri.host == null) return immutableListOf(Proxy.NO_PROXY)

    // proxySelector只会在未指定过proxy的情况下生效,设置方法见13
    //默认没有设置,则返回Proxy.DIRECT
    val proxiesOrNull = address.proxySelector.select(uri)
    if (proxiesOrNull.isNullOrEmpty()) return immutableListOf(Proxy.NO_PROXY)

    return proxiesOrNull.toImmutableList()
  }

  eventListener.proxySelectStart(call, url)
  proxies = selectProxies()
  nextProxyIndex = 0
  eventListener.proxySelectEnd(call, url, proxies)
}

12.指定proxy

OkHttpClient client = new OkHttpClient().newBuilder()
        .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("xxxx",444)))
        .build();

13.设置proxySelector

OkHttpClient client = new OkHttpClient().newBuilder()
        .proxySelector(new ProxySelector() {
            @Override
            public List<Proxy> select(URI uri) {
                return null;
            }

            @Override
            public void connectFailed(URI uri, SocketAddress socketAddress, IOException e) {

            }
        })
        .build();

14.localRouteSelector.next()

@Throws(IOException::class)
operator fun next(): Selection {
  if (!hasNext()) throw NoSuchElementException()
  val routes = mutableListOf<Route>()
  //先从proxies列表找,proxies优先
  while (hasNextProxy()) {
    //找到下一个proxy,找到后会获取到它的ip地址列表,存放在inetSocketAddresses集合中
    //见15
    val proxy = nextProxy()
    for (inetSocketAddress in inetSocketAddresses) {
      //遍历所有ip地址集合,用每一个ip对象构建一条线路出来
      val route = Route(address, proxy, inetSocketAddress)
      if (routeDatabase.shouldPostpone(route)) {
        //这里第一次一定是空,routeDatabase中有一个集合,用来存储失败过的路线
        postponedRoutes += route
      } else {
        //加入集合
        routes += route
      }
    }

    if (routes.isNotEmpty()) {
      break
    }
  }
  //再从postponedRoutes列表中找,postponedRoutes作为兜底使用,postponedRoutes存储的是
  //失败过的线路,假如没有其他可用线路,那么失败过的线路会有再尝试的机会
  if (routes.isEmpty()) {
    routes += postponedRoutes
    postponedRoutes.clear()
  }
  //封装为Selection对象返回
  return Selection(routes)
}

15.nextProxy

@Throws(IOException::class)
private fun nextProxy(): Proxy {
  if (!hasNextProxy()) {
    throw SocketException(
        "No route to ${address.url.host}; exhausted proxy configurations: $proxies")
  }
  //选出一个Proxy
  val result = proxies[nextProxyIndex++]
  //dns解析得到ip地址列表,见16.
  resetNextInetSocketAddress(result)
  return result
}

16.resetNextInetSocketAddress

resetNextInetSocketAddress包含了dns请求,获取到所有host下的ip,然后将所有ip存入到inetSocketAddresses集合中。

@Throws(IOException::class)
private fun resetNextInetSocketAddress(proxy: Proxy) {
  val mutableInetSocketAddresses = mutableListOf<InetSocketAddress>()
  inetSocketAddresses = mutableInetSocketAddresses

  val socketHost: String
  val socketPort: Int
  //当前因为默认是DIRECT,所以if满足条件
  if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
    socketHost = address.url.host
    socketPort = address.url.port
  } else {
    //如果明确指定了代理,指定的方式见12,则使用代理的host和port
    val proxyAddress = proxy.address()
    require(proxyAddress is InetSocketAddress) {
      "Proxy.address() is not an InetSocketAddress: ${proxyAddress.javaClass}"
    }
    socketHost = proxyAddress.socketHost
    socketPort = proxyAddress.port
  }
  //验证port是否合法
  if (socketPort !in 1..65535) {
    throw SocketException("No route to $socketHost:$socketPort; port is out of range")
  }

  if (proxy.type() == Proxy.Type.SOCKS) {
    mutableInetSocketAddresses += InetSocketAddress.createUnresolved(socketHost, socketPort)
  } else {
    //canParseAsIpAddress方法验证下这个host是否满足ip的格式
    val addresses = if (socketHost.canParseAsIpAddress()) {
      listOf(InetAddress.getByName(socketHost))
    } else {
      eventListener.dnsStart(call, socketHost)

      //不是ip,需要先通过dns请求,拿到对应的ip地址,见17
      val result = address.dns.lookup(socketHost)
      if (result.isEmpty()) {
        throw UnknownHostException("${address.dns} returned no addresses for $socketHost")
      }

      eventListener.dnsEnd(call, socketHost, result)
      result
    }
    //将ip存入到列表inetSocketAddresses中
    for (inetAddress in addresses) {
      mutableInetSocketAddresses += InetSocketAddress(inetAddress, socketPort)
    }
  }
}

17.Dns.lookup

获取ip地址

override fun lookup(hostname: String): List<InetAddress> {
  try {
    return InetAddress.getAllByName(hostname).toList()
  } catch (e: NullPointerException) {
    throw UnknownHostException("Broken system behaviour for dns lookup of $hostname").apply {
      initCause(e)
    }
  }
}

返回的是一个ip列表,如:

result = {ArrayList@17243}  size = 8
 0 = {Inet4Address@17250} "juejin.cn/140.207.236.146"
 1 = {Inet4Address@17251} "juejin.cn/140.207.236.147"
 2 = {Inet4Address@17252} "juejin.cn/140.207.236.227"
 3 = {Inet4Address@17253} "juejin.cn/140.207.236.228"
 4 = {Inet4Address@17254} "juejin.cn/140.207.236.229"
 5 = {Inet4Address@17255} "juejin.cn/140.207.236.230"
 6 = {Inet4Address@17256} "juejin.cn/140.207.236.231"
 7 = {Inet4Address@17257} "juejin.cn/140.207.236.232"

18.connect

走到connect方法,本次的分析算是进入深水区了,真正开始执行http连接逻辑。

fun connect(
  connectTimeout: Int,
  readTimeout: Int,
  writeTimeout: Int,
  pingIntervalMillis: Int,
  connectionRetryEnabled: Boolean,
  call: Call,
  eventListener: EventListener
) {
  check(protocol == null) { "already connected" }

  var routeException: RouteException? = null
  //ConnectionSpec对象列表,每个ConnectionSpec封装了socket连接配置,对于https,包含了
  //tls的版本和客户端支持的加密套件(cipher suites),见19
  val connectionSpecs = route.address.connectionSpecs
  //创建一个配置选择器,有点类似上边的RouteSelector
  val connectionSpecSelector = ConnectionSpecSelector(connectionSpecs)
  //如果不是https,sslSocketFactory会为null
  if (route.address.sslSocketFactory == null) {
    //判断socket配置中是否包含明文传输配置,不包含就报路由错误问题
    if (ConnectionSpec.CLEARTEXT !in connectionSpecs) {
      throw RouteException(UnknownServiceException(
          "CLEARTEXT communication not enabled for client"))
    }
    val host = route.address.url.host
    //验证是否配置了支持明文传输,对于Android 9 或更高版本为目标平台来说,默认
    //isCleartextTrafficPermitted为false,不支持直接使用http请求,需要做配置
    //见20
    if (!Platform.get().isCleartextTrafficPermitted(host)) {
      throw RouteException(UnknownServiceException(
          "CLEARTEXT communication to $host not permitted by network security policy"))
    }
  } else {
    //protocols表示支持的http协议版本,如http/1.1、h2
    //如果http请求里包涵了“h2_prior_knowledge”协议,代表是一个支持明文的http2请求
    //抛出异常,不允许使用
    if (Protocol.H2_PRIOR_KNOWLEDGE in route.address.protocols) {
      throw RouteException(UnknownServiceException(
          "H2_PRIOR_KNOWLEDGE cannot be used with HTTPS"))
    }
  }

  while (true) {
    try {
      //如果目标地址是Https协议,但是又通过Http协议代理的话,需要就建立隧道连接,
      //本次调试是不满足的,看else
      if (route.requiresTunnel()) {
        //建立隧道连接,见22
        connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener)
        if (rawSocket == null) {
          // We were unable to connect the tunnel but properly closed down our resources.
          break
        }
      } else {
        //建立普通socket连接,见21
        connectSocket(connectTimeout, readTimeout, call, eventListener)
      }
      //建立连接之后,再创建协议见24
      establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)
      eventListener.connectEnd(call, route.socketAddress, route.proxy, protocol)
      break
    } catch (e: IOException) {
      socket?.closeQuietly()
      rawSocket?.closeQuietly()
      socket = null
      rawSocket = null
      source = null
      sink = null
      handshake = null
      protocol = null
      http2Connection = null
      allocationLimit = 1

      eventListener.connectFailed(call, route.socketAddress, route.proxy, null, e)

      if (routeException == null) {
        routeException = RouteException(e)
      } else {
        routeException.addConnectException(e)
      }

      if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
        throw routeException
      }
    }
  }

  if (route.requiresTunnel() && rawSocket == null) {
    throw RouteException(ProtocolException(
        "Too many tunnel connections attempted: $MAX_TUNNEL_ATTEMPTS"))
  }

  idleAtNs = System.nanoTime()
}

19.ConnectionSpec

截屏2023-12-22 上午7.39.09.png

20.支持明文传输配置

<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
    <application android:networkSecurityConfig="@xml/network_security_config">
            ...
    </application>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="system" />
            <certificates src="user" />
        </trust-anchors>
    </base-config>
</network-security-config>

21.connectSocket

connectSocket方法用于通过socket完成完整HTTP或HTTPS连接所需的所有工作,创建socket对象,设置超时时间,调用connect进行连接,最后拿到socket的输入输出流。

@Throws(IOException::class)
private fun connectSocket(
  connectTimeout: Int,
  readTimeout: Int,
  call: Call,
  eventListener: EventListener
) {
  val proxy = route.proxy
  val address = route.address

  val rawSocket = when (proxy.type()) {
    //通过socketFactory创建socket,本次调试proxy type为direct,所以走这里
    Proxy.Type.DIRECT, Proxy.Type.HTTP -> address.socketFactory.createSocket()!!
    //直接创建socket
    else -> Socket(proxy)
  }
  this.rawSocket = rawSocket

  eventListener.connectStart(call, route.socketAddress, proxy)
  //设置读取超时时间
  rawSocket.soTimeout = readTimeout
  try {
    //根据不同平台做不同的连接,对于Android平台,有两种Platform:
    //Android10Platform, Android10及以上
    //AndroidPlatform,Android10以下
    //connectSocket见23,其实就是调了下socket的connect方法
    Platform.get().connectSocket(rawSocket, route.socketAddress, connectTimeout)
  } catch (e: ConnectException) {
    throw ConnectException("Failed to connect to ${route.socketAddress}").apply {
      initCause(e)
    }
  }

  try {
    //返回从一个可以读取socket内容的缓冲区
    source = rawSocket.source().buffer()
    //返回一个可以向socket写入内容的缓存区
    sink = rawSocket.sink().buffer()
  } catch (npe: NullPointerException) {
    if (npe.message == NPE_THROW_WITH_NULL) {
      throw IOException(npe)
    }
  }
}

22.connectTunnel

什么是隧道连接?

@Throws(IOException::class)
private fun connectTunnel(
  connectTimeout: Int,
  readTimeout: Int,
  writeTimeout: Int,
  call: Call,
  eventListener: EventListener
) {
  var tunnelRequest: Request = createTunnelRequest()
  val url = tunnelRequest.url
  for (i in 0 until MAX_TUNNEL_ATTEMPTS) {
    connectSocket(connectTimeout, readTimeout, call, eventListener)
    tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url)
        ?: break // Tunnel successfully created.

    rawSocket?.closeQuietly()
    rawSocket = null
    sink = null
    source = null
    eventListener.connectEnd(call, route.socketAddress, route.proxy, null)
  }
}

23.connectSocket

可以看到不管是Android10以上,还是Android10以下,connect的方式区别不大,唯一不同的地方是10以下有一个android 8.0的异常兼容处理。

Platform

@Throws(IOException::class)
open fun connectSocket(socket: Socket, address: InetSocketAddress, connectTimeout: Int) {
  socket.connect(address, connectTimeout)
}

Android10Platform

Android10Platform中没有重写connectSocket方法,所以它使用的是Platform中的connect方法。

AndroidPlatform

@Throws(IOException::class)
override fun connectSocket(
  socket: Socket,
  address: InetSocketAddress,
  connectTimeout: Int
) {
  try {
    socket.connect(address, connectTimeout)
  } catch (e: ClassCastException) {
    // On android 8.0, socket.connect throws a ClassCastException due to a bug
    // see https://issuetracker.google.com/issues/63649622
    if (Build.VERSION.SDK_INT == 26) {
      throw IOException("Exception in connect", e)
    } else {
      throw e
    }
  }
}

24.establishProtocol

@Throws(IOException::class)
private fun establishProtocol(
  connectionSpecSelector: ConnectionSpecSelector,
  pingIntervalMillis: Int,
  call: Call,
  eventListener: EventListener
) {
  //如果不是https连接
  if (route.address.sslSocketFactory == null) {
    //h2_prior_knowledge表示http2支持明文连接,既然支持明文连接,那就不需要tls连接了
    //直接调用startHttp2进行http2连接
    if (Protocol.H2_PRIOR_KNOWLEDGE in route.address.protocols) {
      //rawSocket就是刚刚建立了连接的socket
      socket = rawSocket
      //设置协议类型为H2_PRIOR_KNOWLEDGE
      protocol = Protocol.H2_PRIOR_KNOWLEDGE
      //建立http2连接,见26
      startHttp2(pingIntervalMillis)
      return
    }
    //既然不是https,也不是h2_prior_knowledge,那么就将协议版本设置为HTTP_1_1
    socket = rawSocket
    protocol = Protocol.HTTP_1_1
    return
  }

  eventListener.secureConnectStart(call)
  //tls连接,见25
  connectTls(connectionSpecSelector)
  eventListener.secureConnectEnd(call, handshake)
  //如果是http2协议,就开始http2连接
  if (protocol === Protocol.HTTP_2) {
    //建立http2连接,见26
    startHttp2(pingIntervalMillis)
  }
}

25.connectTls

tls连接,是https请求前进行的安全连接,https是运行在tls之上的http请求,tls来确保http请求的安全性。tls连接可以简单理解为,通过非对称加密与服务端协商密钥,然后用协商出来的密钥对数据进行对称加密后与服务端通信,双方通过散列函数验证数据的完整性。

@Throws(IOException::class)
private fun connectTls(connectionSpecSelector: ConnectionSpecSelector) {
  val address = route.address
  val sslSocketFactory = address.sslSocketFactory
  var success = false
  var sslSocket: SSLSocket? = null
  try {
    // 封装已建立连接的socket,得到一个sslSocket
    sslSocket = sslSocketFactory!!.createSocket(
        rawSocket, address.url.host, address.url.port, true /* autoClose */) as SSLSocket

    // 通过前边创建的配置选择器配置sslSocket,见27
    val connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket)
    //如果支持tls扩展配置,还要再配置,见31
    if (connectionSpec.supportsTlsExtensions) {
      Platform.get().configureTlsExtensions(sslSocket, address.url.host, address.protocols)
    }

    // socket开始进行三次握手
    sslSocket.startHandshake()
    val sslSocketSession = sslSocket.session
    //拿到握手信息,unverifiedHandshake是一个Handshake对象,是一个未经验证的握手信息
    val unverifiedHandshake = sslSocketSession.handshake()
    //验证socket的证书是否可用于目标主机
    if (!address.hostnameVerifier!!.verify(address.url.host, sslSocketSession)) {
      val peerCertificates = unverifiedHandshake.peerCertificates
      if (peerCertificates.isNotEmpty()) {
        val cert = peerCertificates[0] as X509Certificate
        throw SSLPeerUnverifiedException("""
            |Hostname ${address.url.host} not verified:
            |    certificate: ${CertificatePinner.pin(cert)}
            |    DN: ${cert.subjectDN.name}
            |    subjectAltNames: ${OkHostnameVerifier.allSubjectAltNames(cert)}
            """.trimMargin())
      } else {
        throw SSLPeerUnverifiedException(
            "Hostname ${address.url.host} not verified (no certificates)")
      }
    }

    val certificatePinner = address.certificatePinner!!
    //走到这里说明验证通过了,构建一个新的Handshake
    handshake = Handshake(unverifiedHandshake.tlsVersion, unverifiedHandshake.cipherSuite,
        unverifiedHandshake.localCertificates) {
      certificatePinner.certificateChainCleaner!!.clean(unverifiedHandshake.peerCertificates,
          address.url.host)
    }

    // 校验证书
    certificatePinner.check(address.url.host) {
      handshake!!.peerCertificates.map { it as X509Certificate }
    }

    val maybeProtocol = if (connectionSpec.supportsTlsExtensions) {
      Platform.get().getSelectedProtocol(sslSocket)
    } else {
      null
    }
    //保存socket
    socket = sslSocket
    //一个可以读取socket内容的缓冲区
    source = sslSocket.source().buffer()
    //一个可以向socket写入内容的缓存区
    sink = sslSocket.sink().buffer()
    //记录使用的http协议版本,默认使用1.1
    protocol = if (maybeProtocol != null) Protocol.get(maybeProtocol) else Protocol.HTTP_1_1
    //成功完成握手
    success = true
  } finally {
    if (sslSocket != null) {
      Platform.get().afterHandshake(sslSocket)
    }
    if (!success) {
      sslSocket?.closeQuietly()
    }
  }
}

26.startHttp2

@Throws(IOException::class)
private fun startHttp2(pingIntervalMillis: Int) {
  //socket、source、sink都在握手阶段赋过值了
  val socket = this.socket!!
  val source = this.source!!
  val sink = this.sink!!
  socket.soTimeout = 0 
  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
  //connection可以并发的最大连接数,
  this.allocationLimit = Http2Connection.DEFAULT_SETTINGS.getMaxConcurrentStreams()
  //发送任何初始帧并开始从远程对等端读取帧
  http2Connection.start()
}

27.configureSecureSocket

configureSecureSocket是从connectionSpecSelector中的connectionSpecs集合选出一个和sslSocket匹配的ConnectionSpec配置,并对sslSocket进行配置,配置了哪些东西呢?其实也就是将tls版本列表和加密方法列表设置给socket,需要注意的是,并非将整个ConnectionSpec配置给socket,而是取ConnectionSpec和socket支持的tls信息相交的部分信息设置给socket。

@Throws(IOException::class)
fun configureSecureSocket(sslSocket: SSLSocket): ConnectionSpec {
  var tlsConfiguration: ConnectionSpec? = null
  for (i in nextModeIndex until connectionSpecs.size) {
    val connectionSpec = connectionSpecs[i]
    //遍历connectionSpecs集合,从中选出一个合适的配置,见28
    if (connectionSpec.isCompatible(sslSocket)) {
      //找到了一个合适的ConnectionSpec,给tlsConfiguration赋值
      tlsConfiguration = connectionSpec
      nextModeIndex = i + 1
      break
    }
  }

  if (tlsConfiguration == null) {
    throw UnknownServiceException("Unable to find acceptable protocols. isFallback=$isFallback," +
        " modes=$connectionSpecs," +
        " supported protocols=${sslSocket.enabledProtocols!!.contentToString()}")
  }
  //isFallbackPossible方法的大意就是判断下connectionSpecs列表中剩余的配置还有没有跟
  //这个socket匹配的,有就返回true,没有就返回false,相当于对其他配置也判断isCompatible
  isFallbackPossible = isFallbackPossible(sslSocket)
  //应用配置,也就是将tls版本列表和加密方法列表设置给socket,
  //第一次执行默认isFallback为false,见29
  tlsConfiguration.apply(sslSocket, isFallback)
  //返回这个配置
  return tlsConfiguration
}

28.isCompatible

ConnectionSpec连接配置怎么才算和socket匹配呢?需要满足两点,第一,ConnectionSpec支持的所有tls版本和socket需要的tls版本中必须有交集;第二,ConnectionSpec支持的加密方法也必须和socket需要的加密方法有交集。

举个例子,ConnectionSpec支持的tls版本有:TLSv1.3、TLSv1.2,而socket支持的tls版本有TLSv1.3、TLSv1.2、TLSv1.1、TLSv1,二者的交集就是TLSv1.3、TLSv1.2,也就是说使用TLSv1.3或TLSv1.2双方都是支持的。ConnectionSpec支持的加密方法列表如下:

0 = "TLS_AES_128_GCM_SHA256"
1 = "TLS_AES_256_GCM_SHA384"
2 = "TLS_CHACHA20_POLY1305_SHA256"
3 = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
4 = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
5 = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
6 = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
7 = "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
8 = "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
9 = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
10 = "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"
11 = "TLS_RSA_WITH_AES_128_GCM_SHA256"
12 = "TLS_RSA_WITH_AES_256_GCM_SHA384"
13 = "TLS_RSA_WITH_AES_128_CBC_SHA"
14 = "TLS_RSA_WITH_AES_256_CBC_SHA"
15 = "SSL_RSA_WITH_3DES_EDE_CBC_SHA"

而socket支持的加密列表方法如下:

0 = "TLS_AES_128_GCM_SHA256"
1 = "TLS_AES_256_GCM_SHA384"
2 = "TLS_CHACHA20_POLY1305_SHA256"
3 = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
4 = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
5 = "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
6 = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
7 = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
8 = "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
9 = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"
10 = "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
11 = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
12 = "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"
13 = "TLS_RSA_WITH_AES_128_GCM_SHA256"
14 = "TLS_RSA_WITH_AES_256_GCM_SHA384"
15 = "TLS_RSA_WITH_AES_128_CBC_SHA"
16 = "TLS_RSA_WITH_AES_256_CBC_SHA"
17 = "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"

所以加密方法也是包含交集的,这个时候说明ConnectionSpec和socket就是匹配的,可以用这个ConnectionSpec来配置socket。

fun isCompatible(socket: SSLSocket): Boolean {
  //不是tsl不需要配置,直接返回
  if (!isTls) {
    return false
  }
  //判断二者支持的tls版本是否有交集
  if (tlsVersionsAsString != null &&
      !tlsVersionsAsString.hasIntersection(socket.enabledProtocols, naturalOrder())) {
    return false
  }
  //判断二者的加密方法列表是否有交集
  if (cipherSuitesAsString != null &&
      !cipherSuitesAsString.hasIntersection(
          socket.enabledCipherSuites, CipherSuite.ORDER_BY_NAME)) {
    return false
  }
  //都满足说明是匹配的,返回true
  return true
}

29.apply

从ConnectionSpec列表中选出一个ConnectionSpec配置,并将其tls版本信息和加密方法信息设置给socket。

internal fun apply(sslSocket: SSLSocket, isFallback: Boolean) {
  //返回一个ConnectionSpec对象,见30
  val specToApply = supportedSpec(sslSocket, isFallback)
  
  if (specToApply.tlsVersions != null) {
    //将选中的ConnectionSpec对象支持的tls版本列表信息设置给socket
    sslSocket.enabledProtocols = specToApply.tlsVersionsAsString
  }

  if (specToApply.cipherSuites != null) {
    //将选中的ConnectionSpec对象支持的加密方法列表设置给socket
    sslSocket.enabledCipherSuites = specToApply.cipherSuitesAsString
  }
}

30.supportedSpec

private fun supportedSpec(sslSocket: SSLSocket, isFallback: Boolean): ConnectionSpec {
  //取socket和ConnectionSpec二者的加密方法列表交集,得到一个新的数组赋值给cipherSuitesIntersection
  var cipherSuitesIntersection = if (cipherSuitesAsString != null) {
    sslSocket.enabledCipherSuites.intersect(cipherSuitesAsString, CipherSuite.ORDER_BY_NAME)
  } else {
    sslSocket.enabledCipherSuites
  }
  //取socket和ConnectionSpec二者支持的tls版本的交集,得到一个新的数组赋值给tlsVersionsIntersection
  val tlsVersionsIntersection = if (tlsVersionsAsString != null) {
    sslSocket.enabledProtocols.intersect(tlsVersionsAsString, naturalOrder())
  } else {
    sslSocket.enabledProtocols
  }

  // 当isFallback为true,并且supportedCipherSuites中包含TLS_FALLBACK_SCSV的时候,
  // 给cipherSuitesIntersection再拼一个TLS_FALLBACK_SCSV
  val supportedCipherSuites = sslSocket.supportedCipherSuites
  val indexOfFallbackScsv = supportedCipherSuites.indexOf(
      "TLS_FALLBACK_SCSV", CipherSuite.ORDER_BY_NAME)
  if (isFallback && indexOfFallbackScsv != -1) {
    cipherSuitesIntersection = cipherSuitesIntersection.concat(
        supportedCipherSuites[indexOfFallbackScsv])
  }
  //用二者交集构建一个新的ConnectionSpec返回
  return Builder(this)
      .cipherSuites(*cipherSuitesIntersection)
      .tlsVersions(*tlsVersionsIntersection)
      .build()
}

31.configureTlsExtensions

对于Android平台,最终会分两种实现方式android10以下AndroidSocketAdapter,Android10及以上Android10SocketAdapter,这里已Android10SocketAdapter为例:

Android10SocketAdapter

@SuppressLint("NewApi")
override fun configureTlsExtensions(
  sslSocket: SSLSocket,
  hostname: String?,
  protocols: List<Protocol>
) {
  try {
    SSLSockets.setUseSessionTickets(sslSocket, true)
    val sslParameters = sslSocket.sslParameters
    // applicationProtocols = "h2"、"http/1.1"
    //Enable ALPN. ALPN (Application Layer Protocol Negotiation)是TLS的扩展,
    //允许在安全连接的基础上进行应用层协议的协商。ALPN支持任意应用层协议的协商,
    //目前应用最多是HTTP2的协商。
    sslParameters.applicationProtocols = Platform.alpnProtocolNames(protocols).toTypedArray()
    //给socket设置sslParameters对象参数
    sslSocket.sslParameters = sslParameters
  } catch (iae: IllegalArgumentException) {
    throw IOException("Android internal error", iae)
  }
}

总结

ConnectInterceptor的逻辑主要在于发起请求前获取connection的操作,获取connection的途径有三种,首先是直接从当前请求对象call上拿connection对象,假如这个connection对象满足复用条件的话,就会直接使用这个conniption;假如这一步拿不到一个有效的connection则会去连接池中获取可复用的连接对象;假如连接池中也没有可用的对象,才会去创建一个新的连接,新连接创建之后进行三次握手,握手完成之后如果是https连接会再进行tls握手,进行加密密钥的协商,将这些都准备好,这个连接也就算正式进入可用状态,连接创建后会加入连接池,以达到后边复用的目的。

本篇虽然分析的已经相对细致,但其实对于http的编解码流程还是没有深入的,感兴趣的可以自行阅读相关代码。


标签:开源框架系列拦截器安卓系统kotlin


程序开发学习排行
最近发表
网站分类
标签列表