一、OkHttp是什么?
Github的原文是:Square’s meticulous HTTP client for the JVM, Android, and GraalVM.
即 Square为JVM、Android和GraalVM精心设计的HTTP客户端。
通过OkHttp的发展史,我们看到,最开始是Square公司觉得Android提供的HttpClient和HttpUrlConnection使用不够便捷,便开发了OkHttp,封装了HttpClient和HttpUrlConnection,后来Apache的HttpClient被摒弃了,OkHttp只保留了HttpUrlConnection,再后来Square觉得HttpUrlConnection也不好用,就摒弃了它,自行开发了HTTP链接的操作。Android官方也于Android4.4发布时,
将HttpUrlConnection的实现换成了OkHttp框架的方式。
二、简单使用步骤
OkHttp的使用文档已经写的很清楚了,我就不花太多时间讲解使用方式。
1.引入库
implementation("com.squareup.okhttp3:okhttp:4.11.0")//OkHttp
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")//Log工具
引入OkHttp4.x 的库会自动引入Okio(一个高效的I/O操作库)和Kotlin标准库。
2.网络请求
//配置OkHttpClient
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor())//添加日志拦截器
.build()
//配置Request
val request = Request.Builder()
.url("https://www.wanandroid.com/article/list/0/json")
.build()
//进行请求
okHttpClient.newCall(request)
.enqueue(object : Callback{
override fun onFailure(call: Call, e: IOException) {
//请求失败信息
}
override fun onResponse(call: Call, response: Response) {
//返回的Response
}
})
三、源码分析
通过API我们知道,进行接口访问的方法是通过Call的enqueue()进行异步请求或者execute()进行同步请求,Call的实现类是RealCall,我们先从enqueue()作为突破口。
RealCall.class
//异步请求
override fun enqueue(responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
//调用了Dispatcher的enqueue()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
进入Dispatcher
Dispatcher.class,起到线程调度的作用,本质是利用Java的Executor,此处就不详细介绍多线程的知识了。
//异步请求入口
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
//将请求加入到准备队列中
readyAsyncCalls.add(call)
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
// the same host.
if (!call.call.forWebSocket) {
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
//执行队列中的请求
promoteAndExecute()
}
//执行队列中的请求
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
//遍历准备请求队列的数据
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
//判断正在请求的链接数是否超过最大值,超过后直接break,剩余的call需要等待
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
//判断对同一主机的链接是否超过最大值,超过后continue下一个call
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
//俩个值都没超过
i.remove()
//增加对同一主机的链接数
asyncCall.callsPerHost.incrementAndGet()
//添加到准备执行的请求集合
executableCalls.add(asyncCall)
//添加到请求中的集合
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
//执行符合要求的各个请求
asyncCall.executeOn(executorService)
}
return isRunning
}
进入AsyncCall
AsyncCall.class,实现了Runnable接口
fun executeOn(executorService: ExecutorService) {
......
try {
//线程池执行this,即执行AsyncCall的run方法
executorService.execute(this)
success = true
} catch (e: RejectedExecutionException) {
......
}
}
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
......
try {
//OkHttp的最关键方法,即请求方法
val response = getResponseWithInterceptorChain()
signalledCallback = true
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
......
}
}
}
以上是通过Call的enqueue()方法异步调用了RealCall的getResponseWithInterceptorChain(),其实我们如果以execute()作为突破口的话,会发现也是进入这个方法。
RealCall.class
//同步请求入口
override fun execute(): Response {
check(executed.compareAndSet(false, true)) { "Already Executed" }
timeout.enter()
callStart()
try {
client.dispatcher.executed(this)
//OkHttp的最关键方法,即请求方法
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
现在我们就看一下getResponseWithInterceptorChain()到底是如何进行网络请求的。
RealCall.class
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
//第一部分:按照顺序整合所有的Interceptor
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors //自己添加的Interceptor
interceptors += RetryAndFollowUpInterceptor(client) //重试和重定向操作
interceptors += BridgeInterceptor(client.cookieJar) //拼接报文信息的操作
interceptors += CacheInterceptor(client.cache) //缓存相关操作
interceptors += ConnectInterceptor //建立网络连接的操作
if (!forWebSocket) {
//针对网络层的自己添加的Interceptor,一般在调试接口时使用,可以拿到完整的请求报文和返回报文
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket) //建立网络请求的操作
//第二部分:生成RealInterceptorChain对象
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
//第三部分:执行Chain的proceed()方法,开始链式调用各个Interceptor,从自己添加的Interceptors
//(如果没添加默认从RetryAndFollowUpInterceptor)开始,直到CallServerInterceptor进行网络请求,
//再一步一步返回到第一个Interceptor,最后返回Response
//通过责任链模式,实现了完整的从 网络链接->网络请求->请求返回 的全过程
var calledNoMoreExchanges = false
try {
val response = chain.proceed(originalRequest)
if (isCanceled()) {
response.closeQuietly()
throw IOException("Canceled")
}
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
接下来我们分析一下在不添加自定义Interceptor情况下,OkHttp是如何实现完整网络请求的
按照顺序首先是RetryAndFollowUpInterceptor.class,
RetryAndFollowUpInterceptor.class
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
var request = chain.request
val call = realChain.call
var followUpCount = 0
var priorResponse: Response? = null
var newExchangeFinder = true
var recoveredFailures = listOf<IOException>()
while (true) { //开启循环处理重试和重定向问题,直到返回数据或者遇到其他错误return
//做链接准备,根据newExchangeFinder判断是否需要创建ExchangeFinder,
//ExchangeFinder是用来寻找合适的交换链接的,ConnectInterceptor会用到
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
var response: Response
var closeActiveExchange = true
try {
if (call.isCanceled()) {
throw IOException("Canceled")
}
try {
//链式调用下一个Interceptor
response = realChain.proceed(request)
newExchangeFinder = true
} catch (e: RouteException) {
//处理某一条链接线路出错的情况,判断是否可重试
//因为当以域名进行请求的时候,可能DNS解析到多个ip,还因为可能存在Proxy,所以会有多条链接线路出现
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
throw e.firstConnectException.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e.firstConnectException
}
newExchangeFinder = false
continue
} catch (e: IOException) {
//处理IO异常,例如请求超时、HTTP协议出错、SSL认证出错等等,判断是否可重试
// An attempt to communicate with a server failed. The request may have been sent.
if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
throw e.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e
}
newExchangeFinder = false
continue
}
//以下操作是访问没有出错,但是可能出现了重定向
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build()
}
val exchange = call.interceptorScopedExchange
/**
* 依据各种HTTP状态码判断是否需要重定向
*
* 此方法不仅处理了30X系列code,还处理了
* 401-Unauthorized、 407-Proxy Authentication Required、 408-Request Time-Out、
* 421-HTTP2中使用相同证书子域名 SSL配置不一致导致、503-Service Unavailable
*
* 1. 针对401、407,需要用户配置了OkHttpClient的authenticator(授权认证)、proxyAuthenticator(代理认证)
* 2. 针对30X系列的判断条件(解释一下,307类似302,但不允许重定向为GET,308类似301,也不允许重定向为GET)
* a.用户设置可以重定向
* b.response中header的"location"可以解析
* c.支持重定向地址的协议
* d.返回scheme和请求的scheme必须一致且配置允许ssl重定向
* e.针对存在请求body的,
* 如果可以重定向为GET且不是307,308,构建GET请求并置空RequestBody
* 否则如果是307或者308或者Method是PROPFIND,构建原有请求并添加RequestBody
* 否则构建原有请求并置空RequestBody
* f.跨主机重定向时,删除所有身份验证头。
* 3. 针对408的判断条件
* a.用户是否设置可以重定向
* b.如果当前Response不为空,且只允许请求一次,不允许重定向
* c.本次请求结果和上一次请求结果均超时,不允许重定向
* d.解析结果相应头Retry-After(响应的 HTTP 报头指示所述用户代理应该多长时间使一个后续请求之前等待
* 如果当前Retry-After大于0,不允许重定向
* 4. 针对503的判断条件
* a.本次请求结果和上一次请求结果均返回503,不允许重定向
* b.如果返回的Retry-After为0,没有任何延迟,则返回Request对象,否则不允许重定向
* 5. 针对421(在HTTP2多路复用时,发现连接到的服务器不正确,则会由服务器返回响应码 421,客户端收到后会重新建立连接并且发送相同的请求)
* a.如果当前Response不为空,且只允许请求一次,不允许重定向
* b.如果exchange为空或者exchange不是多路复用的,不允许重定向
* c.否则进行重定向
*/
val followUp = followUpRequest(response, exchange)
//没有重定向,放回Response
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
call.timeoutEarlyExit()
}
closeActiveExchange = false
return response
}
//如果request body只能发送一次,也返回Response,isOneShot()默认都是false,除非被子类重写
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
closeActiveExchange = false
return response
}
response.body?.closeQuietly()
//重定向超过20次抛出异常
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
//用重定向的request去进行请求
request = followUp
//将本次的响应记录到临时变量中
priorResponse = response
} finally {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
//异常是否可修复
private fun recover(
e: IOException,
call: RealCall,
userRequest: Request,
requestSendStarted: Boolean
): Boolean {
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure) return false
// We can't send the request body again.
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false
// This exception is fatal.
// isRecoverable()判断了ProtocolException、SocketTimeoutException、
// SSLHandshakeException && CertificateException、SSLPeerUnverifiedException 四种情况不可修复
if (!isRecoverable(e, requestSendStarted)) return false
// No more routes to attempt.
if (!call.retryAfterFailure()) return false
// For failure recovery, use the same route selector with a new connection.
return true
}
RetryAndFollowUpInterceptor
前置工作:做链接准备
中置工作:链式调用下一个Interceptor
后置工作:通过Response进行重试和重定向操作。
然后我们看下一个Interceptor:BridgeInterceptor
BridgeInterceptor.class
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val userRequest = chain.request()
val requestBuilder = userRequest.newBuilder()
// 前置部分:拼接Header信息,包括Content-Type、Content-Length、使用gzip等
val body = userRequest.body
if (body != null) {
val contentType = body.contentType()
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString())
}
val contentLength = body.contentLength()
if (contentLength != -1L) {
requestBuilder.header("Content-Length", contentLength.toString())
requestBuilder.removeHeader("Transfer-Encoding")
} else {
requestBuilder.header("Transfer-Encoding", "chunked")
requestBuilder.removeHeader("Content-Length")
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", userRequest.url.toHostHeader())
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive")
}
var transparentGzip = false
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true
requestBuilder.header("Accept-Encoding", "gzip")
}
val cookies = cookieJar.loadForRequest(userRequest.url)
if (cookies.isNotEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies))
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", userAgent)
}
//中置部分:链式调用下一个Interceptor
val networkResponse = chain.proceed(requestBuilder.build())
//后置部分:1.保存cookie信息 2.如果response使用了gzip,进行解析
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
val strippedHeaders = networkResponse.headers.newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build()
responseBuilder.headers(strippedHeaders)
val contentType = networkResponse.header("Content-Type")
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
return responseBuilder.build()
}
BridgeInterceptor主要起到代码和HTTP请求的转换作用。
接下来我们看下一个:CacheInterceptor
CacheInterceptor.class
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val call = chain.call()
//获取缓存
val cacheCandidate = cache?.get(chain.request())
val now = System.currentTimeMillis()
//获取缓存策略
//通过各种条件获取到CacheStrategy对象,包含2个属性networkRequest和cacheResponse
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
val networkRequest = strategy.networkRequest
val cacheResponse = strategy.cacheResponse
cache?.trackResponse(strategy)
val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE
if (cacheCandidate != null && cacheResponse == null) {
// The cache candidate wasn't applicable. Close it.
cacheCandidate.body?.closeQuietly()
}
// 如果networkRequest和cacheResponse都为null,则直接返回出错
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(HTTP_GATEWAY_TIMEOUT)
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build().also {
listener.satisfactionFailure(call, it)
}
}
// 如果networkRequest为空,即不需要网络请求,直接返回缓存数据
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build().also {
listener.cacheHit(call, it)
}
}
if (cacheResponse != null) {
listener.cacheConditionalHit(call, cacheResponse)
} else if (cache != null) {
listener.cacheMiss(call)
}
var networkResponse: Response? = null
try {
//networkRequest不为null,进行网络请求
//链式调用下一个Interceptor
networkResponse = chain.proceed(networkRequest)
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
cacheCandidate.body?.closeQuietly()
}
}
//如果本身有缓存,进行网络请求后,http code是304(表示无修改),则更新缓存响应并返回
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
val response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers, networkResponse.headers))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis)
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
networkResponse.body!!.close()
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache!!.trackConditionalCacheHit()
cache.update(cacheResponse, response)
return response.also {
listener.cacheHit(call, it)
}
} else {
cacheResponse.body?.closeQuietly()
}
}
val response = networkResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
// cache不为空,有请求头和缓存策略时,通过cache.put进行缓存
if (cache != null) {
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
val cacheRequest = cache.put(response)
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
// This will log a conditional cache miss only.
listener.cacheMiss(call)
}
}
}
//如果HttpMethod判断缓存无效,清除缓存
if (HttpMethod.invalidatesCache(networkRequest.method)) {
try {
cache.remove(networkRequest)
} catch (_: IOException) {
// The cache cannot be written.
}
}
}
return response
}
CacheInterceptor
前置工作:判断是否从缓存中返回Response
中置工作:链式调用下一个Interceptor
后置工作:判断是否需要从Response中进行缓存数据或缓存更新,只缓存GET请求的数据
然后我们看下一个Interceptor:ConnectInterceptor
ConnectInterceptor.class
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//前置操作:获取或者生成链接
val realChain = chain as RealInterceptorChain
val exchange = realChain.call.initExchange(chain)//调用RealCall的initExchange(后续讲解)
val connectedChain = realChain.copy(exchange = exchange)
//中置操作:链式调用下一个Interceptor
return connectedChain.proceed(realChain.request)
}
RealCall.class
//初始化Exchange
internal fun initExchange(chain: RealInterceptorChain): Exchange {
......
//在RetryAndFollowupInterceptor中创建
val exchangeFinder = this.exchangeFinder!!
//获取ExchangeCodec对象,根据Protocol分为Http1ExchangeCodec和Http2ExchangeCodec(后续讲解)
val codec = exchangeFinder.find(client, chain)
//生成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")
return result
}
ExchangeFinder.class
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"
)
//返回ExchangeCodec
return resultConnection.newCodec(client, chain)
} catch (e: RouteException) {
......
}
}
@Throws(IOException::class)
private fun findHealthyConnection(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean,
doExtensiveHealthChecks: Boolean
): RealConnection {
while (true) {
//找到链接(后面有解析)
val candidate = findConnection(
connectTimeout = connectTimeout,
readTimeout = readTimeout,
writeTimeout = writeTimeout,
pingIntervalMillis = pingIntervalMillis,
connectionRetryEnabled = connectionRetryEnabled
)
// 判断链接是否健康
if (candidate.isHealthy(doExtensiveHealthChecks)) {
return candidate
}
// 不健康时,添加标记,以便移出pool
candidate.noNewExchanges()
//根据条件遍历所有Route
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") //异常,用尽所有route
}
}
@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) {
//判断链接是否可复用,不可用就release
if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
toClose = call.releaseConnectionNoEvents()
}
}
//如果没被release,复用
if (call.connection != null) {
check(toClose == null)
//返回call之前使用的链接
return callConnection
}
//如果release了,关闭socket
toClose?.closeQuietly()
eventListener.connectionReleased(call, callConnection)
}
//当前call没有可以复用的链接,后续需要去获取一个新的链接:1.从pool中获取 2.创建新的
refusedStreamCount = 0
connectionShutdownCount = 0
otherFailureCount = 0
//第一次从pool中寻找:(后续讲解)
//尝试从链接池中获取一个链接使用,routes参数为null,即只寻找符合HTTP1的链接
if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
val result = call.connection!!
eventListener.connectionAcquired(call, result)
//返回链接池中的可复用链接
return result
}
//从pool中没有得到复用的链接,需要获取一个route对象去生成链接
val routes: List<Route>?
val route: Route
if (nextRouteToTry != null) {
//如果之前有一个可以生成有效链接的route,使用它,避免重复选择route的操作
routes = null
route = nextRouteToTry!!
nextRouteToTry = null
} else if (routeSelection != null && routeSelection!!.hasNext()) {
//如果有selection,且selection中还有未尝试的route, 获取一个route
routes = null
route = routeSelection!!.next()
} else {
//生成新的RouteSelector,一个RouteSelector包含多个Selection,一个Selection包含多个Route
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")
//第二次从pool中寻找
//此时routes不为null,可以获取符合链接复用的HTTP2的链接以及符合条件的HTTP1的链接
if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
val result = call.connection!!
eventListener.connectionAcquired(call, result)
return result
}
//第二次从pool中仍没获取复用的链接,获取一个route,去生成新链接
route = localRouteSelection.next()
}
//生成新链接对象
val newConnection = RealConnection(connectionPool, route)
call.connectionToCancel = newConnection //默认链接可能创建失败
try {
//创造链接,阻塞式的(后续讲解)
newConnection.connect(
connectTimeout,
readTimeout,
writeTimeout,
pingIntervalMillis,
connectionRetryEnabled,
call,
eventListener
)
} finally {
call.connectionToCancel = null //没出错说明创建了有效链接,将connectionToCancel置为null
}
//将能生成有效链接的route从失败的list中移除
call.client.routeDatabase.connected(newConnection.route())
//第三次从pool种寻找:只拿多路复用的链接
//如果在创建完链接时发现有多路复用的HTTP2链接,复用,并关闭刚创建的HTTP2链接
if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
val result = call.connection!!
//如果拿到可复用的链接,缓存当前route,因为当前route可以创建有效链接,避免下一次再重复寻找route
nextRouteToTry = route
newConnection.socket().closeQuietly()
eventListener.connectionAcquired(call, result)
//返回复用链接
return result
}
//第三次从链接池仍没有找到可复用的链接,将新建的链接放入链接池,并将链接赋值给当前call
synchronized(newConnection) {
connectionPool.put(newConnection)
call.acquireConnectionNoEvents(newConnection)
}
eventListener.connectionAcquired(call, newConnection)
//返回新链接
return newConnection
}
RealConnectionPoll.class
//从链接池获取符合要求的链接
fun callAcquirePooledConnection(
address: Address,
call: RealCall,
routes: List<Route>?,
requireMultiplexed: Boolean
): Boolean {
for (connection in connections) {
synchronized(connection) {
//HTTP2多路复用判断
if (requireMultiplexed && !connection.isMultiplexed) return@synchronized
//链接是否合格,主要判断内容:1.链接数没超过上限 2.连接方式要一致
if (!connection.isEligible(address, routes)) return@synchronized
//复用链接
call.acquireConnectionNoEvents(connection)
return true
}
}
return false
}
RealConnection.class
//链接是否可用
internal fun isEligible(address: Address, routes: List<Route>?): Boolean {
assertThreadHoldsLock()
//链接中的call数量超标或不许添加新请求时,不合格
//HTTP1中allocationLimit为1,HTTP2中okhttp设置是4
if (calls.size >= allocationLimit || noNewExchanges) return false
//如果除了host之外的数据有不同的,不合格,包括如下参数:
//this.proxyAuthenticator
//this.protocols
//this.connectionSpecs
//this.proxySelector
//this.proxy
//this.sslSocketFactory
//this.hostnameVerifier
//this.certificatePinner
//this.url.port
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.
}
//如果Host不相同,判断是否是HTTP2的链接
if (http2Connection == null) return false
// route必须是同样的address,包括ip和port
if (routes == null || !routeMatchesAny(routes)) return false
// HostnameVerifier要一样,且已建立链接的证书支持当前url
if (address.hostnameVerifier !== OkHostnameVerifier) return false
if (!supportsUrl(address.url)) return false
// CertificatePinner也要支持当前host
try {
address.certificatePinner!!.check(address.url.host, handshake()!!.peerCertificates)
} catch (_: SSLPeerUnverifiedException) {
return false
}
//符合复用要求
return true // The caller's address can be carried by this connection.
}
//真正建立链接
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
val connectionSpecs = route.address.connectionSpecs
val connectionSpecSelector = ConnectionSpecSelector(connectionSpecs)
......
while (true) {
try {
//使用HttpTunnel,用Proxy将HTTP代理为HTTPS
if (route.requiresTunnel()) {
//创建HttpTunnel链接
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链接
connectSocket(connectTimeout, readTimeout, call, eventListener)
}
//依据协议判断是否需要开启HTTP2、是否建立TLS链接
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)
eventListener.connectEnd(call, route.socketAddress, route.proxy, protocol)
break
} catch (e: IOException) {
......
}
}
}
ConnectInterceptor
前置工作:获取或者创建一个可用的链接,一共五步
- 判断当前call是否有可用的connection,有就返回
- 从pool中获取符合HTTP1的可复用链接,有就返回
- 从pool中获取符合HTTP1和HTTP2的可复用链接,有就返回
- 根据route创建一个新的connection
- 从pool中获取符合HTTP2的可复用链接,有就返回,没有则返回第4步创建的connection
中置工作:链式调用下一个Interceptor
后置工作:无
然后我们看最后一个CallServerInterceptor
CallServerInterceptor.class
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val exchange = realChain.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 {
//写入Request的Header
exchange.writeRequestHeaders(request)
//判断是否需要写入RequestBody
if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return
// what we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
exchange.flushRequest()
responseBuilder = exchange.readResponseHeaders(expectContinue = true)
exchange.responseHeadersStart()
invokeStartEvent = false
}
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 {
// Write the request body if the "Expect: 100-continue" expectation was met.
val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
requestBody.writeTo(bufferedRequestBody)
bufferedRequestBody.close()
}
} else {
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
}
//下面是解析Response的代码,也是利用ExchangeCodec对象
try {
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
if (invokeStartEvent) {
exchange.responseHeadersStart()
invokeStartEvent = false
}
}
var response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
var code = response.code
if (shouldIgnoreAndWaitForRealResponse(code)) {
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)
response = if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response.newBuilder()
.body(EMPTY_RESPONSE)
.build()
} else {
response.newBuilder()
.body(exchange.openResponseBody(response))
.build()
}
if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
"close".equals(response.header("Connection"), ignoreCase = true)) {
exchange.noNewExchangesOnConnection()
}
if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) {
throw ProtocolException(
"HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}")
}
//返回Response,倒序执行各个Interceptor的后置工作。
return response
} catch (e: IOException) {
if (sendRequestException != null) {
sendRequestException.addSuppressed(e)
throw sendRequestException
}
throw e
}
}
CallServerInterceptor的
前置工作:Encodes HTTP requests
中置工作:无,网络请求是最后一步
后置工作:Decodes HTTP responses
CallServerInterceptor基于Exchange中的ExchangeCodec对象(细分为HTTP1ExchangeCodec和HTTP2ExchangeCodec)来最终实现网络请求的,I/O部分是用的Okio框架(在这里就不分析了)。
至此,基于OkHttp五大拦截器的网络请求过程全部讲完了。
总结
-
使用OkHttp框架的优势、好处有哪些
a. 自带重试和重定向处理
b. 内置链接池,支持复用,且支持HTTP2,可以使用多路复用
c. 使用Okio,高效的I/O操作
d. http2.0协议支持对Header压缩。okhttp提供了Gzip压缩body体
e. 实现了请求的缓存处理 -
OkHttp中使用了哪些设计模式可以借鉴学习
a. 构造者模式:Request 和 Response 使用了 Builder 模式来创建,这样可以很方便地设置各种参数,并且保持了对象的不可变性。
b. 责任链模式:通过设置多个拦截器Interceptor,将网络请求细分为多个步骤,每个步骤实现一部分功能,每个半部分都能处理请求和响应。
c. 观察者模式:网络请求使用Callback,当异步请求完成时,注册的观察者会收到通知,并执行相应的回调。
如果对Retrofit框架有兴趣的朋友可以看一下我的另一篇文章,介绍了基于OkHttp的Retrofit框架的使用和源码分析。