Android开源框架系列-OkHttp4.11.0(kotlin版)- 拦截器分析之ConnectInterceptor
作者:访客发布时间:2023-12-24分类:程序开发学习浏览:111
前言
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
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的编解码流程还是没有深入的,感兴趣的可以自行阅读相关代码。
相关推荐
- 轻松上手:
(三)笔记可再编辑 - 如何在iPhone,iPad和Android上使用WordPress应用程序
- 一款简单高效的Android异步框架
- [Android][NDK][Cmake]一文搞懂Android项目中的Cmake
- Android---View中的setMinWidth与setMinimumWidth的踩坑记录
- Android广播如何解决Sending non-protected broadcast问题
- 有关Android Binder面试,你未知的9个秘密
- 开启Android学习之旅-2-架构组件实现数据列表及添加(kotlin)
- Android低功耗蓝牙开发总结
- Android 通知文本颜色获取
- 程序开发学习排行
-
- 1鸿蒙HarmonyOS:Web组件网页白屏检测
- 2HTTPS协议是安全传输,为啥还要再加密?
- 3HarmonyOS鸿蒙应用开发——数据持久化Preferences
- 4记解决MaterialButton背景颜色与设置值不同
- 5鸿蒙HarmonyOS实战-ArkUI组件(RelativeContainer)
- 6鸿蒙HarmonyOS实战-ArkUI组件(Stack)
- 7[Android][NDK][Cmake]一文搞懂Android项目中的Cmake
- 8Android广播如何解决Sending non-protected broadcast问题
- 9鸿蒙HarmonyOS实战-ArkUI组件(mediaquery)
- 最近发表
-
- WooCommerce最好的WordPress常用插件下载博客插件模块的相关产品
- 羊驼机器人最好的WordPress常用插件下载博客插件模块
- IP信息记录器最好的WordPress常用插件下载博客插件模块
- Linkly for WooCommerce最好的WordPress常用插件下载博客插件模块
- 元素聚合器Forms最好的WordPress常用插件下载博客插件模块
- Promaker Chat 最好的WordPress通用插件下载 博客插件模块
- 自动更新发布日期最好的WordPress常用插件下载博客插件模块
- WordPress官方最好的获取回复WordPress常用插件下载博客插件模块
- Img to rss最好的wordpress常用插件下载博客插件模块
- WPMozo为Elementor最好的WordPress常用插件下载博客插件模块添加精简版