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通信过程中的数据帧。