为什么拦截器把正常请求也拦截了_你想要的系列:网络请求框架OkHttp3全解系列 (四)拦截器详解2:连接、请求服务(重点)...

635c4d887793b37b4c683d868a8ac6a8.png点击上方蓝字可以关注哦~

在本系列的上一篇文章你想要的系列:网络请求框架OkHttp3全解系列 - (三)拦截器详解1:重试重定向、桥、缓存(重点)中,我们分析了OkHttp拦截器链中的前三个拦截器:RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor,它们在请求建立连接之前做了一些预处理。

如果请求经过这三个拦截器后,要继续往下传递,说明是需要进行网络请求的(缓存不能直接满足),也就是今天要分析的内容——剩下的两个拦截器:ConnectInterceptor、CallServerInterceptor,分别负责 连接建立请求服务读写

背景 - HTTP协议发展

在讲解拦截器之前,我们有必要了解http协议相关背景知识,因为okhttp的网络连接正是基于此实现的。HTTP协议经历了以下三个版本阶段。

HTTP1.0

HTTP1.0中,一次请求 会建立一个TCP连接,请求完成后主动断开连接。这种方法的好处是简单,各个请求互不干扰。
但每次请求都会经历 3次握手、2次或4次挥手的连接建立和断开过程——极大影响网络效率和系统开销。

2fcede1945e25e370c939fbed126980c.png
http1.0

HTTP1.1

HTTP1.1中,解决了HTTP1.0中连接不能复用的问题,支持持久连接——使用keep-alive机制:一次HTTP请求结束后不会立即断开TCP连接,如果此时有新的HTTP请求,且其请求的Host同上次请求相同,那么会直接复用TCP连接。这样就减少了建立和关闭连接的消耗和延迟。keep-alive机制在HTTP1.1中是默认打开的——即在请求头添加:connection:keep-alive。(keep-alive不会永久保持连接,它有一个保持时间,可在不同的服务器软件(如Apache)中设定这个时间)

8eaa5c7e5221f92fd9ab2aa1eb424b17.png
http1.1

HTTP2.0

HTTP1.1中,连接的复用是串行的:一个请求建立了TCP连接,请求完成后,下一个相同host的请求继续使用这个连接。 但客户端想 同时 发起多个并行请求,那么必须建立多个TCP连接。将会产生网络延迟、增大网路开销。

并且HTTP1.1不会压缩请求和响应报头,导致了不必要的网络流量;HTTP1.1不支持资源优先级导致底层TCP连接利用率低下。在HTTP2.0中,这些问题都会得到解决,HTTP2.0主要有以下特性

  • 新的二进制格式(Binary Format):http/1.x使用的是明文协议,其协议格式由三部分组成:request line,header,body,其协议解析是基于文本,但是这种方式存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合;基于这种考虑,http/2.0的协议解析决定采用二进制格式,实现方便且健壮

  • 多路复用(MultiPlexing):即连接共享,使用streamId用来区分请求,一个request对应一个stream并分配一个id,这样一个TCP连接上可以有多个stream,每个stream的frame可以随机的混杂在一起,接收方可以根据stream id将frame再归属到各自不同的request里面

  • 优先级和依赖(Priority、Dependency):每个stream都可以设置优先级和依赖,优先级高的stream会被server优先处理和返回给客户端,stream还可以依赖其它的sub streams;优先级和依赖都是可以动态调整的,比如在APP上浏览商品列表,用户快速滑动到底部,但是前面的请求已经发出,如果不把后面的优先级设高,那么当前浏览的图片将会在最后展示出来,显然不利于用户体验

  • header压缩:http2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小

  • 重置连接:很多APP里都有停止下载图片的需求,对于http1.x来说,是直接断开连接,导致下次再发请求必须重新建立连接;http2.0引入RST_STREAM类型的frame,可以在不断开连接的前提下取消某个request的stream

其中涉及了两个新的概念:

  • 数据流-stream:基于TCP连接之上的逻辑双向字节流,用于承载双向消息,对应一个请求及其响应。客户端每发起一个请求就建立一个数据流,后续该请求及其响应的所有数据都通过该数据流传输。每个数据流都有一个唯一的标识符和可选的优先级信息。

  • 帧-frame:HTTP/2的最小数据切片单位,承载着特定类型的数据,例如 HTTP 标头、消息负载,等等。 来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装,从而在宏观上实现了多个请求或响应并行传输的效果。

    e7764e05251af355e570ad2df524730c.png
    http2.0

这里的 多路复用机制 就实现了 在同一个TCP连接上 多个请求 并行执行。

无论是HTTP1.1的Keep-Alive机制还是HTTP2.0的多路复用机制,在实现上都需要引入连接池来维护网络连接。下面就开始分析 OkHttp中的连接池实现——连接拦截器ConnectInterceptor

ConnectInterceptor

连接拦截器ConnectInterceptor代码如下:

 1//打开到目标服务的连接、处理下一个拦截器
2public final class ConnectInterceptor implements Interceptor {
3  public final OkHttpClient client;
4
5  public ConnectInterceptor(OkHttpClient client) {
6    this.client = client;
7  }
8
9  @Override public Response intercept(Chain chain) throws IOException {
10    RealInterceptorChain realChain = (RealInterceptorChain) chain;
11    Request request = realChain.request();
12    Transmitter transmitter = realChain.transmitter();
13
14    // We need the network to satisfy this request. Possibly for validating a conditional GET.
15    boolean doExtensiveHealthChecks = !request.method().equals("GET");
16    Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
17
18    return realChain.proceed(request, transmitter, exchange);
19  }
20}

代码很少,主要是使用transmitter.newExchange获取Exchange实例,并作为参数调用拦截器链的proceed的方法。注意到前面分析过的拦截器调用的proceed方法是一个参数的,而这里是三个参数的。这是因为下一个拦截器(如果没有配置网络拦截器的话,就是CallServerInterceptor,也是最后一个)需要进行真正的网络IO操作,而 Exchange(意为交换)主要作用就是真正的IO操作:写入请求、读取响应(会在下一个拦截器做介绍)。

实际上获取Exchange实例的逻辑处理都封装在Transmitter中了。前面的文章提到过Transmitter,它是“发射器”,是把 请求 从应用端 发射到 网络层,它持有请求的 连接、请求、响应 和 流,一个请求对应一个Transmitter实例,一个数据流。下面就看下它的newExchange方法:

 1  Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    
2    synchronized (connectionPool) {
3      if (noMoreExchanges) {
4        throw new IllegalStateException("released");
5      }
6      if (exchange != null) {
7        throw new IllegalStateException("cannot make a new request because the previous response "
8            + "is still open: please call response.close()");
9      }
10    }
11
12    ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
13    Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
14
15    synchronized (connectionPool) {
16      this.exchange = result;
17      this.exchangeRequestDone = false;
18      this.exchangeResponseDone = false;
19      return result;
20    }
21  }

若是第一次请求,前面两个if是没走进去的。接着看到使用exchangeFinder的find方法获取到了ExchangeCodec实例,然后作为参数构建了Exchange实例,并返回。嗯,看起来也很简单的样子。 注意到这个方法里涉及了连接池RealConnectionPool、交换寻找器ExchangeFinder、交换编解码ExchangeCodec、交换管理Exchange这几个类(翻译成这样尽力了?,意会吧)。

  • RealConnectionPool,连接池,负责管理请求的连接,包括新建、复用、关闭,理解上类似线程池。

  • ExchangeCodec,接口类,负责真正的IO操作—写请求、读响应,实现类有Http1ExchangeCodec、Http2ExchangeCodec,分别对应HTTP1.1协议、HTTP2.0协议。

  • Exchange,管理IO操作,可以理解为 数据流,是ExchangeCodec的包装,增加了事件回调;一个请求对应一个Exchange实例。传给下个拦截器CallServerInterceptor使用。

  • ExchangeFinder,(从连接池中)寻找可用TCP连接,然后通过连接得到ExchangeCodec。

ExchangeFinder

ExchangeFinder的作用从名字就可以看出——Exchange寻找者,本质是为请求寻找一个TCP连接。如果已有可用连接就直接使用,没有则打开一个新的连接。 一个网络请求的执行,需要先有一个指向目标服务的TCP连接,然后再进行写请求、读响应的IO操作。ExchangeFinder是怎么寻找的呢?继续往下看~

我们先看exchangeFinder初始化的地方:

1  public void prepareToConnect(Request request) {
    
2    ...
3    this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
4        call, eventListener);
5  }

看到这里应该会想起上一篇文章中分析RetryAndFollowUpInterceptor时提到过,prepareToConnect这个方法作用是连接准备,就是创建了ExchangeFinder实例。主要到传入的参数有connectionPool、createAddress方法返回的Address、call、eventListener。connectionPool是连接池,稍后分析,先看下createAddress方法:

 1  private Address createAddress(HttpUrl url) {
    
2    SSLSocketFactory sslSocketFactory = null;
3    HostnameVerifier hostnameVerifier = null;
4    CertificatePinner certificatePinner = null;
5    if (url.isHttps()) {
6      sslSocketFactory = client.sslSocketFactory();
7      hostnameVerifier = client.hostnameVerifier();
8      certificatePinner = client.certificatePinner();
9    }
10
11    return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
12        sslSocket
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值