从网络请求过程看OkHttp拦截器( 五 )


还有个问题就是为什么有当前连接??明明还没开始连接也没有获取连接啊,怎么连接就被赋值了?
还记得 重试和重定向 拦截器吗?对了,就是当请求失败需要重试的时候或者重定向的时候,这时候连接还在呢,是可以直接进行复用的 。

  • 2和3、从连接池中获取可用连接
第2步和第3步都是从连接池获取连接,有什么不一样吗?
connectionPool.callAcquirePooledConnection(address, call, null, false)connectionPool.callAcquirePooledConnection(address, call, routes, false)好像多了一个 routes 字段?
这里涉及到HTTP/2的一个技术,叫做 HTTP/2 CONNECTION COALESCING (连接合并),什么意思呢?
假设有两个域名,可以解析为相同的IP地址,并且是可以用相同的TLS证书(比如通配符证书),那么客户端可以重用相同的 TCP连接 从这两个域名中获取资源 。
再看回我们的连接池,这个 routes 就是当前域名(主机名)可以被解析的 ip地址 集合,这两个方法的区别也就是一个传了路由地址,一个没有传 。
继续看
callAcquirePooledConnection 代码:
internal fun isEligible(address: Address, routes: List<Route>?): Boolean {if (address.url.host == this.route().address.url.host) {return true}//HTTP/2 CONNECTION COALESCINGif (http2Connection == null) return falseif (routes == null || !routeMatchesAny(routes)) return falseif (address.hostnameVerifier !== OkHostnameVerifier) return falsereturn true}1)判断主机名、端口号等,如果请求完全相同就直接返回这个连接 。
2)如果主机名不同,还可以判断是不是 HTTP/2 请求,如果是就继续判断路由地址,证书,如果都能匹配上,那么这个连接也是可用的 。
  • 4、创建新连接
如果没有从连接池中获取到新连接,那么就创建一个新连接,这里就不多说了,其实就是调用到 socket.connect 进行TCP连接 。
  • 5、再从连接池获取一次连接,防止在新建连接过程中有其他竞争连接被创建了
创建了新连接,为什么还要去连接池获取一次连接呢?
因为在这个过程中,有可能有其他的请求和你一起创建了新连接,所以我们需要再去取一次连接,如果有可以用的,就直接用它,防止资源浪费 。
其实这里又涉及到HTTP2的一个知识点: 多路复用  。
简单的说,就是不需要当前连接的上一个请求结束之后再去进行下一次请求,只要有连接就可以直接用 。
HTTP/2引入二进制数据帧和流的概念,其中帧对数据进行顺序标识,这样在收到数据之后,就可以按照序列对数据进行合并,而不会出现合并后数据错乱的情况 。同样是因为有了序列,服务器就可以并行的传输数据,这就是流所做的事情 。
所以在 HTTP/2 中可以保证在同一个域名只建立一路连接,并且可以并发进行请求 。
  • 6、新连接放入连接池,并返回
最后一步好理解吧,走到这里说明就要用这个新连接了,那么就把它存到连接池,返回这个连接 。
这个拦截器确实麻烦,大家好好梳理下吧,我也再来个图:
从网络请求过程看OkHttp拦截器

文章插图
 
IO拦截器(CallServerInterceptor)连接拿到了,编码解码器有了,剩下的就是发数据,读数据了,也就是跟 I/O 相关的工作 。
class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {@Throws(IOException::class)override fun intercept(chain: Interceptor.Chain): Response {//写header数据exchange.writeRequestHeaders(request)//写body数据if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()requestBody.writeTo(bufferedRequestBody)} else {exchange.noRequestBody()}//结束请求if (requestBody == null || !requestBody.isDuplex()) {exchange.finishRequest()}//获取响应数据var response = responseBuilder.request(request).handshake(exchange.connection.handshake()).build()var code = response.coderesponse = response.newBuilder().body(exchange.openResponseBody(response)).build()return response}}这个拦截器 倒是没干什么活,之前的拦截器兄弟们都把准备工作干完了,它就调用下 exchange 类的各种方法,写入 header,body ,拿到 code,response  。
这活可干的真轻松啊 。
被遗漏的自定义拦截器(networkInterceptors)好了,最后补上这个拦截器 networkInterceptors ,它也是一个自定义拦截器,位于 CallServerInterceptor 之前,属于倒数第二个拦截器 。


推荐阅读