okhttp

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   }

 

上一篇:图解大数据 | 实操案例-Hadoop系统搭建与环境配置


下一篇:每日总结13