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的使用文档已经写的很清楚了,我就不花太多时间讲解使用方式。
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标准库。
//配置OkHttpClient
val okHttpClient = OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor())//添加日志拦截器.build()
//配置Request
val request = Request.Builder().url("").build()
//进行请求
wCall(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(executedpareAndSet(false, true)) { "Already Executed" }callStart()//调用了Dispatcher的enqueue()queue(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) useCallsPerHostFrom(existingCall)}}//执行队列中的请求promoteAndExecute()}//执行队列中的请求private fun promoteAndExecute(): Boolean {this.assertThreadDoesntHoldLock()val executableCalls = mutableListOf<AsyncCall>()val isRunning: Booleansynchronized(this) {//遍历准备请求队列的数据val i = readyAsyncCalls.iterator()while (i.hasNext()) {val asyncCall = i.next()//判断正在请求的链接数是否超过最大值,超过后直接break,剩余的call需要等待if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.//判断对同一主机的链接是否超过最大值,超过后continue下一个callif (() >= 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]//执行符合要求的各个请求uteOn(executorService)}return isRunning}
进入AsyncCall
AsyncCall.class,实现了Runnable接口fun executeOn(executorService: ExecutorService) {......try {//线程池执行this,即执行AsyncCall的run方法ute(this)success = true} catch (e: RejectedExecutionException) {......}}override fun run() {threadName("OkHttp ${redactedUrl()}") {......try {//OkHttp的最关键方法,即请求方法val response = getResponseWithInterceptorChain()signalledCallback = Response(this@RealCall, response)} catch (e: IOException) {......}}}
以上是通过Call的enqueue()方法异步调用了RealCall的getResponseWithInterceptorChain(),其实我们如果以execute()作为突破口的话,会发现也是进入这个方法。
RealCall.class//同步请求入口override fun execute(): Response {check(executedpareAndSet(false, true)) { "Already Executed" }()callStart()try {uted(this)//OkHttp的最关键方法,即请求方法return getResponseWithInterceptorChain()} finally {client.dispatcher.finished(this)}}
现在我们就看一下getResponseWithInterceptorChain()到底是如何进行网络请求的。
RealCall.class@Throws(IOException::class)internal fun getResponseWithInterceptorChain(): Response {//第一部分:按照顺序整合所有的Interceptorval interceptors = mutableListOf<Interceptor>()interceptors += client.interceptors //自己添加的Interceptorinterceptors += RetryAndFollowUpInterceptor(client) //重试和重定向操作interceptors += kieJar) //拼接报文信息的操作interceptors += CacheInterceptor(client.cache) //缓存相关操作interceptors += ConnectInterceptor //建立网络连接的操作if (!forWebSocket) {//针对网络层的自己添加的Interceptor,一般在调试接口时使用,可以拿到完整的请求报文和返回报文interceptors += clientworkInterceptors }interceptors += CallServerInterceptor(forWebSocket) //建立网络请求的操作//第二部分:生成RealInterceptorChain对象val chain = RealInterceptorChain(call = this,interceptors = interceptors,index = 0,exchange = null,request = originalRequest,connectTimeoutMillis = tTimeoutMillis,readTimeoutMillis = adTimeoutMillis,writeTimeoutMillis = client.writeTimeoutMillis)//第三部分:执行Chain的proceed()方法,开始链式调用各个Interceptor,从自己添加的Interceptors//(如果没添加默认从RetryAndFollowUpInterceptor)开始,直到CallServerInterceptor进行网络请求,//再一步一步返回到第一个Interceptor,最后返回Response//通过责任链模式,实现了完整的从 网络链接->网络请求->请求返回 的全过程var calledNoMoreExchanges = falsetry {val response = chain.proceed(originalRequest)if (isCanceled()) {response.closeQuietly()throw IOException("Canceled")}return response} catch (e: IOException) {calledNoMoreExchanges = truethrow 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 RealInterceptorChainvar request = questval call = realChain.callvar followUpCount = 0var priorResponse: Response? = nullvar newExchangeFinder = truevar recoveredFailures = listOf<IOException>()while (true) { //开启循环处理重试和重定向问题,直到返回数据或者遇到其他错误return//做链接准备,根据newExchangeFinder判断是否需要创建ExchangeFinder,//ExchangeFinder是用来寻找合适的交换链接的,ConnectInterceptor会用到NetworkInterceptorExchange(request, newExchangeFinder)var response: Responsevar closeActiveExchange = truetry {if (call.isCanceled()) {throw IOException("Canceled")}try {//链式调用下一个Interceptorresponse = 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 = falsecontinue} 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 = falsecontinue}//以下操作是访问没有出错,但是可能出现了重定向// Attach the prior response if it exists. Such responses never have a body.if (priorResponse != null) {response = wBuilder().wBuilder().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)//没有重定向,放回Responseif (followUp == null) {if (exchange != null && exchange.isDuplex) {call.timeoutEarlyExit()}closeActiveExchange = falsereturn response}//如果request body只能发送一次,也返回Response,isOneShot()默认都是false,除非被子类重写val followUpBody = followUp.bodyif (followUpBody != null && followUpBody.isOneShot()) {closeActiveExchange = falsereturn response}response.body?.closeQuietly()//重定向超过20次抛出异常if (++followUpCount > MAX_FOLLOW_UPS) {throw ProtocolException("Too many follow-up requests: $followUpCount")}//用重定向的request去进行请求request = followUp//将本次的响应记录到临时变量中priorResponse = response} finally {itNetworkInterceptorExchange(closeActiveExchange)}}}//异常是否可修复private fun recover(e: IOException,call: RealCall,userRequest: Request,requestSendStarted: Boolean): Boolean {// The application layer has forbidden retries.if (!OnConnectionFailure) 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 (!AfterFailure()) return false// For failure recovery, use the same route selector with a urn true}
RetryAndFollowUpInterceptor
前置工作:做链接准备
中置工作:链式调用下一个Interceptor
后置工作:通过Response进行重试和重定向操作。
然后我们看下一个Interceptor:BridgeInterceptor
BridgeInterceptor.class@Throws(IOException::class)override fun intercept(chain: Interceptor.Chain): Response {val userRequest = quest()val requestBuilder = wBuilder()// 前置部分:拼接Header信息,包括Content-Type、Content-Length、使用gzip等val body = userRequest.bodyif (body != null) {val contentType = tType()if (contentType != null) {requestBuilder.header("Content-Type", String())}val contentLength = tLength()if (contentLength != -1L) {requestBuilder.header("Content-Length", String())veHeader("Transfer-Encoding")} else {requestBuilder.header("Transfer-Encoding", "chunked")veHeader("Content-Length")}}if (userRequest.header("Host") == null) {requestBuilder.header("Host", HostHeader())}if (userRequest.header("Connection") == null) {requestBuilder.header("Connection", "Keep-Alive")}var transparentGzip = falseif (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {transparentGzip = truerequestBuilder.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)}//中置部分:链式调用下一个Interceptorval networkResponse = chain.proceed(requestBuilder.build())//后置部分:1.保存cookie信息 2.如果response使用了gzip,进行解析iveHeaders(userRequest.url, networkResponse.headers)val responseBuilder = wBuilder().request(userRequest)if (transparentGzip &&"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&networkResponse.promisesBody()) {val responseBody = networkResponse.bodyif (responseBody != null) {val gzipSource = GzipSource(responseBody.source())val strippedHeaders = wBuilder().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?.quest())val now = System.currentTimeMillis()//获取缓存策略//通过各种条件获取到CacheStrategy对象,包含2个属性networkRequest和cacheResponseval strategy = CacheStrategy.Factory(now, quest(), cacheCandidate)pute()val networkRequest = strategyworkRequestval cacheResponse = strategy.cacheResponsecache?.trackResponse(strategy)val listener = (call as? RealCall)?.eventListener ?: EventListener.NONEif (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().quest()).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? = nulltry {//networkRequest不为null,进行网络请求//链式调用下一个InterceptornetworkResponse = 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 = wBuilder().headers(combine(cacheResponse.headers, networkResponse.headers)).sentRequestAtMillis(networkResponse.sentRequestAtMillis).ivedResponseAtMillis).cacheResponse(stripBody(cacheResponse))workResponse(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))workResponse(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.hod)) {try {ve(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 RealInterceptorChainval exchange = realChain.call.initExchange(chain)//调用RealCall的initExchange(后续讲解)val connectedChain = py(exchange = exchange)//中置操作:链式调用下一个Interceptorreturn connectedChain.quest)}
RealCall.class//初始化Exchangeinternal fun initExchange(chain: RealInterceptorChain): Exchange {......//在RetryAndFollowupInterceptor中创建val exchangeFinder = hangeFinder!! //获取ExchangeCodec对象,根据Protocol分为Http1ExchangeCodec和Http2ExchangeCodec(后续讲解)val codec = exchangeFinder.find(client, chain)//生成Exchange对象val result = Exchange(this, eventListener, exchangeFinder, codec)this.interceptorScopedExchange = hange = resultsynchronized(this) {questBodyOpen = sponseBodyOpen = true}if (canceled) throw IOException("Canceled")return result}
ExchangeFinder.classfun find(client: OkHttpClient,chain: RealInterceptorChain): ExchangeCodec {try {//找到健康的链接val resultConnection = findHealthyConnection(connectTimeout = tTimeoutMillis,readTimeout = adTimeoutMillis,writeTimeout = chain.writeTimeoutMillis,pingIntervalMillis = client.pingIntervalMillis,connectionRetryEnabled = OnConnectionFailure,doExtensiveHealthChecks = hod != "GET")//返回wCodec(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}// 不健康时,添加标记,以便移出NewExchanges()//根据条件遍历所有Routeif (nextRouteToTry != null) continueval routesLeft = routeSelection?.hasNext() ?: trueif (routesLeft) continueval routesSelectionLeft = routeSelector?.hasNext() ?: trueif (routesSelectionLeft) continuethrow 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 = tion if (callConnection != null) {var toClose: Socket? = nullsynchronized(callConnection) {//判断链接是否可复用,不可用就releaseif (NewExchanges || !ute().address.url)) {toClose = leaseConnectionNoEvents()}}//如果没被release,复用if (tion != null) {check(toClose == null)//返回call之前使用的链接return callConnection}//如果release了,关闭sockettoClose?.closeQuietly()tionReleased(call, callConnection)}//当前call没有可以复用的链接,后续需要去获取一个新的链接:1.从pool中获取 2.创建新的refusedStreamCount = 0connectionShutdownCount = 0otherFailureCount = 0//第一次从pool中寻找:(后续讲解)//尝试从链接池中获取一个链接使用,routes参数为null,即只寻找符合HTTP1的链接if (connectionPool.callAcquirePooledConnection(address, call, null, false)) { val result = tion!!tionAcquired(call, result)//返回链接池中的可复用链接return result}//从pool中没有得到复用的链接,需要获取一个route对象去生成链接val routes: List<Route>? val route: Routeif (nextRouteToTry != null) {//如果之前有一个可以生成有效链接的route,使用它,避免重复选择route的操作routes = nullroute = nextRouteToTry!!nextRouteToTry = null} else if (routeSelection != null && routeSelection!!.hasNext()) {//如果有selection,且selection中还有未尝试的route, 获取一个routeroutes = nullroute = routeSelection!!.next()} else {//生成新的RouteSelector,一个RouteSelector包含多个Selection,一个Selection包含多个Routevar localRouteSelector = routeSelectorif (localRouteSelector == null) {localRouteSelector = RouteSelector(address, uteDatabase, call, uteSelector = localRouteSelector}val localRouteSelection = ()routeSelection = localRouteSelectionroutes = utesif (call.isCanceled()) throw IOException("Canceled")//第二次从pool中寻找//此时routes不为null,可以获取符合链接复用的HTTP2的链接以及符合条件的HTTP1的链接if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {val result = tion!!tionAcquired(call, result)return result}//第二次从pool中仍没获取复用的链接,获取一个route,去生成新链接route = ()}//生成新链接对象val newConnection = RealConnection(connectionPool, tionToCancel = newConnection //默认链接可能创建失败try {//创造链接,阻塞式的(后续讲解t(connectTimeout,readTimeout,writeTimeout,pingIntervalMillis,connectionRetryEnabled,call,eventListener)} finally {tionToCancel = null //没出错说明创建了有效链接,将connectionToCancel置为null}//将能生成有效链接的route从失败的list中移除ute())//第三次从pool种寻找:只拿多路复用的链接//如果在创建完链接时发现有多路复用的HTTP2链接,复用,并关闭刚创建的HTTP2链接if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {val result = tion!!//如果拿到可复用的链接,缓存当前route,因为当前route可以创建有效链接,避免下一次再重复寻找routenextRouteToTry = routenewConnection.socket().closeQuietly()tionAcquired(call, result)//返回复用链接return result}//第三次从链接池仍没有找到可复用的链接,将新建的链接放入链接池,并将链接赋值给当前callsynchronized(newConnection) {connectionPool.put(newConnection)call.acquireConnectionNoEvents(newConnection)}tionAcquired(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设置是4if (calls.size >= allocationLimit || noNewExchanges) return false//如果除了host之外的数据有不同的,不合格,包括如下参数://this.proxyAuthenticator//this.protocols//tionSpecs//this.proxySelector//this.proxy//this.sslSocketFactory//this.hostnameVerifier//ificatePinner//this.url.portif (!ute.address.equalsNonHost(address)) return false//如果host相同,合格if (address.url.host == ute().address.url.host) {return true // This connection is a perfect match.}//如果Host不相同,判断是否是HTTP2的链接if (http2Connection == null) return false // route必须是同样的address,包括ip和portif (routes == null || !routeMatchesAny(routes)) return false// HostnameVerifier要一样,且已建立链接的证书支持当前urlif (address.hostnameVerifier !== OkHostnameVerifier) return falseif (!supportsUrl(address.url)) return false// CertificatePinner也要支持当前hosttry {ificatePinner!!.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? = nullval connectionSpecs = tionSpecsval connectionSpecSelector = ConnectionSpecSelector(connectionSpecs)......while (true) {try {//使用HttpTunnel,用Proxy将HTTP代理为HTTPSif (quiresTunnel()) {//创建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, tEnd(call, route.socketAddress, route.proxy, protocol)break} catch (e: IOException) {......}}}
ConnectInterceptor
前置工作:获取或者创建一个可用的链接,一共五步
中置工作:链式调用下一个Interceptor
后置工作:无
然后我们看最后一个CallServerInterceptor
CallServerInterceptor.classoverride fun intercept(chain: Interceptor.Chain): Response {val realChain = chain as RealInterceptorChainval exchange = hange!!val request = questval requestBody = request.bodyval sentRequestMillis = System.currentTimeMillis()var invokeStartEvent = truevar responseBuilder: Response.Builder? = nullvar sendRequestException: IOException? = nulltry {//写入Request的Headerexchange.writeRequestHeaders(request)//判断是否需要写入RequestBodyif (HttpMethod.hod) && 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 = adResponseHeaders(expectContinue = sponseHeadersStart()invokeStartEvent = false}if (responseBuilder == null) {if (requestBody.isDuplex()) {// Prepare a duplex body so that the application can send a request hange.flushRequest()val bufferedRequestBody = ateRequestBody(request, true).buffer()requestBody.writeTo(bufferedRequestBody)} else {// Write the request body if the "Expect: 100-continue" expectation was met.val bufferedRequestBody = ateRequestBody(request, false).buffer()requestBody.writeTo(bufferedRequestBody)bufferedRequestBody.close()}} else {RequestBody()if (!tion.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 NewExchangesOnConnection()}}} else {RequestBody()}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 = adResponseHeaders(expectContinue = false)!!if (invokeStartEvent) {sponseHeadersStart()invokeStartEvent = false}}var response = quest(request).tion.handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build()var code = deif (shouldIgnoreAndWaitForRealResponse(code)) {responseBuilder = adResponseHeaders(expectContinue = false)!!if (invokeStartEvent) {sponseHeadersStart()}response = quest(request).tion.handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build()code = sponseHeadersEnd(response)response = if (forWebSocket && code == 101) {// Connection is upgrading, but we need to ensure interceptors see a non-null wBuilder().body(EMPTY_RESPONSE).build()} else {wBuilder().body(exchange.openResponseBody(response)).build()}if ("close".quest.header("Connection"), ignoreCase = true) ||"close".equals(response.header("Connection"), ignoreCase = true)) {NewExchangesOnConnection()}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框架的使用和源码分析。
本文发布于:2024-01-29 02:16:20,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170646578512010.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |