okhttp时一个http client, 它脱离了对原生的依赖, 从创建socket开始,整套都是自己写的 ,
我们简单使用如下
1 val client = OkHttpClient.Builder().build() 2 val request = Request.Builder().url("http://www.baidu.com").build() 3 4 client.newCall(request).enqueue(object : okhttp3.Callback{ 5 override fun onFailure(call: okhttp3.Call, e: IOException) { 6 } 7 override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) { 8 } 9 })
1, OkHttpClient 相当于配置中⼼,所有的请求都会共享这些配置(例如出错 是否重试、共享的连接池)。 OkHttpClient 中的配置主要有:
Dispatcher dispatcher :调度器,⽤于调度后台发起的⽹络请求, 有后台总请求数和单主机总请求数的控制。
List protocols :⽀持的应⽤层协议,即 HTTP/1.1、 HTTP/2 等。
List connectionSpecs :应⽤层⽀持的 Socket 设置,即使⽤明⽂传输(⽤于 HTTP)还是某个版本的 TLS(⽤于 HTTPS)。
List interceptors :⼤多数时候使⽤的 Interceptor 都应该配置到这⾥。
List networkInterceptors :直接和⽹络请求交互 的 Interceptor 配置到这⾥,例如如果你想查看返回的 301 报⽂或者未解压 的 Response Body,需要在这⾥看。
CookieJar cookieJar :管理 Cookie 的控制器。OkHttp 提供了 Cookie 存取的判断⽀持(即什么时候需要存 Cookie,什么时候需要读取 Cookie,但没有给出具体的存取实现。如果需要存取 Cookie,你得⾃⼰写 实现,例如⽤ Map 存在内存⾥,或者⽤别的⽅式存在本地存储或者数据 库。
Cache cache :Cache 存储的配置。默认是没有,如果需要⽤,得⾃⼰ 配置出 Cache 存储的⽂件位置以及存储空间上限。 HostnameVerifier hostnameVerifier :⽤于验证 HTTPS 握⼿过程 中下载到的证书所属者是否和⾃⼰要访问的主机名⼀致。 CertificatePinner certificatePinner :⽤于设置 HTTPS 握⼿ 过程中针对某个 Host 额外的的 Certificate Public Key Pinner,即把⽹站证 书链中的每⼀个证书公钥直接拿来提前配置进 OkHttpClient ⾥去,作为正 常的证书验证机制之外的⼀次额外验证。 Authenticator authenticator :⽤于⾃动重新认证。配置之后,在 请求收到 401 状态码的响应是,会直接调⽤ authenticator ,⼿动加 ⼊ Authorization header 之后⾃动重新发起请求。
boolean followRedirects :遇到重定向的要求是,是否⾃动 follow。
boolean followSslRedirects 在重定向时,如果原先请求的是 http ⽽重定向的⽬标是 https,或者原先请求的是 https ⽽重定向的⽬标是 http,是否依然⾃动 follow。(记得,不是「是否⾃动 follow HTTPS URL 重定向的意思,⽽是是否⾃动 follow 在 HTTP 和 HTTPS 之间切换的重定 向)
boolean retryOnConnectionFailure :在请求失败的时候是否⾃动 重试。注意,⼤多数的请求失败并不属于 OkHttp 所定义的「需要重试」, 这种重试只适⽤于「同⼀个域名的多个 IP 切换重试」「Socket 失效重试」 等情况。
int connectTimeout :建⽴连接(TCP 或 TLS)的超时时间。
int readTimeout :发起请求到读到响应数据的超时时间。
int writeTimeout :发起请求并被⽬标服务器接受的超时时间。(为 什么?因为有时候对⽅服务器可能由于某种原因⽽不读取你的 Request
2, client.newCall(request)生成了一个RealCall对象,当调⽤ RealCall.execute() 的时 候, RealCall.getResponseWithInterceptorChain() 会被调⽤,它 会发起⽹络请求并拿到返回的响应,装进⼀个 Response 对象并作为返回值返 回; RealCall.enqueue() 被调⽤的时候⼤同⼩异,区别在于 enqueue() 会使⽤ Dispatcher 的线程池来把请求放在后台线程进⾏,但 实质上使⽤的同样也是 getResponseWithInterceptorChain() ⽅法。
3, okhttp内部请求的过程时一个链式结构, getResponseWithInterceptorChain() ⽅法做的事:把所有配置好的 Interceptor 放在⼀个 List ⾥,然后作为参数,创建⼀个 RealInterceptorChain 对象,并调⽤ chain.proceed(request) 来 发起请求和获取响应
RealInterceptorChain 中,多个 Interceptor 会依次调⽤⾃⼰的 intercept() ⽅法。这个⽅法会做三件事: 1. 对请求进⾏预处理 2. 预处理之后,重新调⽤ RealIntercepterChain.proceed() 把请求 交给下⼀个 Interceptor 3. 在下⼀个 Interceptor 处理完成并返回之后,拿到 Response 进⾏后续 处理
当然了,最后⼀个 Interceptor 的任务只有⼀个:做真正的⽹络请求并 拿到响应
我们可以为okhttp配置多个interceptor, 如果我们不配置,默认的Interceptor有五个
1 internal fun getResponseWithInterceptorChain(): Response { 2 // Build a full stack of interceptors. 3 val interceptors = mutableListOf<Interceptor>() 4 interceptors += client.interceptors 5 interceptors += RetryAndFollowUpInterceptor(client) 6 interceptors += BridgeInterceptor(client.cookieJar) 7 interceptors += CacheInterceptor(client.cache) 8 interceptors += ConnectInterceptor 9 if (!forWebSocket) { 10 interceptors += client.networkInterceptors 11 } 12 interceptors += CallServerInterceptor(forWebSocket)
⾸先是开发者使⽤ addInterceptor(Interceptor) 所设置的,它们 会按照开发者的要求,在所有其他 Interceptor 处理之前,进⾏最早的 预处理⼯作,以及在收到 Response 之后,做最后的善后⼯作。如果你有统 ⼀的 header 要添加,可以在这⾥设置;
然后是 RetryAndFollowUpInterceptor :它会对连接做⼀些初始化⼯ 作,并且负责在请求失败时的重试,以及重定向的⾃动后续请求。它的存 在,可以让重试和重定向对于开发者是⽆感知的;
BridgeInterceptor :它负责⼀些不影响开发者开发,但影响 HTTP 交 互的⼀些额外预处理。例如,Content-Length 的计算和添加、gzip 的⽀持 (Accept-Encoding: gzip)、gzip 压缩数据的解包,都是发⽣在这⾥;
CacheInterceptor :它负责 Cache 的处理。把它放在后⾯的⽹络交互 相关 Interceptor 的前⾯的好处是,如果本地有了可⽤的 Cache,⼀个 请求可以在没有发⽣实质⽹络交互的情况下就返回缓存结果,⽽完全不需要 开发者做出任何的额外⼯作,让 Cache 更加⽆感知;
ConnectInterceptor :它负责建⽴连接。在这⾥,OkHttp 会创建出⽹ 络请求所需要的 TCP 连接(如果是 HTTP),或者是建⽴在 TCP 连接之上 的 TLS 连接(如果是 HTTPS),并且会创建出对应的 HttpCodec 对象 (⽤于编码解码 HTTP 请求);
然后是开发者使⽤ addNetworkInterceptor(Interceptor) 所设置 的,它们的⾏为逻辑和使⽤ addInterceptor(Interceptor) 创建的 ⼀样,但由于位置不同,所以这⾥创建的 Interceptor 会看到每个请求 和响应的数据(包括重定向以及重试的⼀些中间请求和响应),并且看到的 是完整原始数据,⽽不是没有加 Content-Length 的请求数据,或者 Body 还没有被 gzip 解压的响应数据。多数情况,这个⽅法不需要被使⽤,不过 如果你要做⽹络调试,可以⽤它;
CallServerInterceptor :它负责实质的请求与响应的 I/O 操作,即 往 Socket ⾥写⼊请求数据,和从 Socket ⾥读取响应数据。
4, 连接池
okhttp内部维护了一个连接池对象, 每当有新的请求时,它会
1)如果连接池中有符合本次请求的连接(ip, 端口,tls协议等都一样, 且没有超过最大连接数), 有的话直接使用
2)尝试从连接池中获取一个不带多路复用的连接
3)如果是http2, 那么再尝试从连接池中获取可以多路复用的连接
4, 如果还拿不到, 那就自己创建一个连接,放进连接池
5, 再从连接池中尝试获取可以多路复用的连接, 如果能拿到,就直接用,并且把自己刚刚创建的那个连接扔掉 (同步块中)
(为什么创建了之后还要再从池里拿? 为了防止多个请求同时创建连接)
1 @Throws(IOException::class) 2 private fun findConnection( 3 connectTimeout: Int, 4 readTimeout: Int, 5 writeTimeout: Int, 6 pingIntervalMillis: Int, 7 connectionRetryEnabled: Boolean 8 ): RealConnection { 9 var foundPooledConnection = false 10 var result: RealConnection? = null 11 var selectedRoute: Route? = null 12 var releasedConnection: RealConnection? 13 val toClose: Socket? 14 synchronized(connectionPool) { 15 if (call.isCanceled()) throw IOException("Canceled") 16 17 releasedConnection = call.connection 18 toClose = if (call.connection != null && 19 (call.connection!!.noNewExchanges || !call.connection!!.supportsUrl(address.url))) { 20 call.releaseConnectionNoEvents() 21 } else { 22 null 23 } 24 25 if (call.connection != null) { 26 // We had an already-allocated connection and it's good. 27 result = call.connection 28 releasedConnection = null 29 } 30 31 if (result == null) { 32 // The connection hasn't had any problems for this call. 33 refusedStreamCount = 0 34 connectionShutdownCount = 0 35 otherFailureCount = 0 36 37 // Attempt to get a connection from the pool. 38 if (connectionPool.callAcquirePooledConnection(address, call, null, false)) { 39 foundPooledConnection = true 40 result = call.connection 41 } else if (nextRouteToTry != null) { 42 selectedRoute = nextRouteToTry 43 nextRouteToTry = null 44 } 45 } 46 } 47 toClose?.closeQuietly() 48 49 if (releasedConnection != null) { 50 eventListener.connectionReleased(call, releasedConnection!!) 51 } 52 if (foundPooledConnection) { 53 eventListener.connectionAcquired(call, result!!) 54 } 55 if (result != null) { 56 // If we found an already-allocated or pooled connection, we're done. 57 return result!! 58 } 59 60 // If we need a route selection, make one. This is a blocking operation. 61 var newRouteSelection = false 62 if (selectedRoute == null && (routeSelection == null || !routeSelection!!.hasNext())) { 63 var localRouteSelector = routeSelector 64 if (localRouteSelector == null) { 65 localRouteSelector = RouteSelector(address, call.client.routeDatabase, call, eventListener) 66 this.routeSelector = localRouteSelector 67 } 68 newRouteSelection = true 69 routeSelection = localRouteSelector.next() 70 } 71 72 var routes: List<Route>? = null 73 synchronized(connectionPool) { 74 if (call.isCanceled()) throw IOException("Canceled") 75 76 if (newRouteSelection) { 77 // Now that we have a set of IP addresses, make another attempt at getting a connection from 78 // the pool. This could match due to connection coalescing. 79 routes = routeSelection!!.routes 80 if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) { 81 foundPooledConnection = true 82 result = call.connection 83 } 84 } 85 86 if (!foundPooledConnection) { 87 if (selectedRoute == null) { 88 selectedRoute = routeSelection!!.next() 89 } 90 91 // Create a connection and assign it to this allocation immediately. This makes it possible 92 // for an asynchronous cancel() to interrupt the handshake we're about to do. 93 result = RealConnection(connectionPool, selectedRoute!!) 94 connectingConnection = result 95 } 96 } 97 98 // If we found a pooled connection on the 2nd time around, we're done. 99 if (foundPooledConnection) { 100 eventListener.connectionAcquired(call, result!!) 101 return result!! 102 } 103 104 // Do TCP + TLS handshakes. This is a blocking operation. 105 result!!.connect( 106 connectTimeout, 107 readTimeout, 108 writeTimeout, 109 pingIntervalMillis, 110 connectionRetryEnabled, 111 call, 112 eventListener 113 ) 114 call.client.routeDatabase.connected(result!!.route()) 115 116 var socket: Socket? = null 117 synchronized(connectionPool) { 118 connectingConnection = null 119 // Last attempt at connection coalescing, which only occurs if we attempted multiple 120 // concurrent connections to the same host. 121 if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) { 122 // We lost the race! Close the connection we created and return the pooled connection. 123 result!!.noNewExchanges = true 124 socket = result!!.socket() 125 result = call.connection 126 127 // It's possible for us to obtain a coalesced connection that is immediately unhealthy. In 128 // that case we will retry the route we just successfully connected with. 129 nextRouteToTry = selectedRoute 130 } else { 131 connectionPool.put(result!!) 132 call.acquireConnectionNoEvents(result!!) 133 } 134 } 135 socket?.closeQuietly() 136 137 eventListener.connectionAcquired(call, result!!) 138 return result!! 139 }