由浅入深了解OkHttp五:OkHttp各拦截器作用介绍

时间:2024-03-13 14:47:34

如果您对这一系列的文章感兴趣,可以关注此专栏:android网络框架OkHttp+Retrofit源码分析

废话不多说先上图:OkHttp拦截器链

由浅入深了解OkHttp五:OkHttp各拦截器作用介绍

结合上图中的说明我们对OkHttp拦截器链上的各个拦截器的作用进行说明

RetryAndFollowUpInterceptor拦截器

retryAndFollowUpInterceptor此拦截器顾名思义就是主要负责失败重连工作,但是并不是所有的网络请求都会进行失败重连的,在此拦截器内部会进行网络请求的异常检测和响应码的判断,如果都在限制范围内,那么就可以进行失败重连

主要执行工作:

1、创建StreamAllocation对象

2、调用RealInterceptorChainrealChain.proceed(request, streamAllocation, null, null); 通过chain对象调用下一个拦截器BridgeInterceptor

3、从下一个拦截器那里接收传递过来的response,根据异常和响应结果判断是否重连pan

4、处理完成之后将response返回给上一个拦截器


BridgeInterceptor拦截器(桥接模式?)
桥接拦截器:主要负责设置内容长度、编码方式、设置gzip压缩、添加请求头、cookie等相关功能
那么它是怎么实现这种桥接转换的呢,因为我们知道除了请求Url之外,浏览器还需要请求头等信息,如下图所示:
由浅入深了解OkHttp五:OkHttp各拦截器作用介绍

桥接拦截器实现的是桥接模式,用来桥接客户端代码和网络代码,或者说是链接客户端代码和网络代码的桥梁

既然BridgeInterceptor会根据用户请求创建真正的Network Request,那么这些请求头信息也是少不了的


       
 @Override public Response intercept(Chain chain) {
            
            //取用构建的Request            Request userRequest = chain.request();
   .....
            //置Host
            if (userRequest.header("Host") == null) {
       
                requestBuilder.header("Host", hostHeader(userRequest.url(), false));}
            //置Connection,Keep-Alive  保持接状            if (userRequest.header("Connection") == null) {
       
                requestBuilder.header("Connection", "Keep-Alive"); }
            // 如果置了压缩,也会构建解压缩参数
            
            boolean transparentGzip = false;
            if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
                
                transparentGzip = true;
                requestBuilder.header("Accept-Encoding", "gzip");
            }
            //上面的代码逻辑也很简单,就是Request置User-Agent、Cookie、Accept-Encoding等相关信息
            // 到此止一个完整的NetWork Request 就构建完            //截器链继续往下运行
            Response networkResponse =chain.proceed(requestBuilder.build());
            
   .....
//判断服器是否支持gzip压缩格式,如果支持kio压缩
   .....
            
        
}
        

主要功能:

1、负责将用户构建的一个Request请求转化为能够进行网络访问的请求(桥接)

2、将这个符合网络请求的Request继续向下传递进行网络请求

3、将网络请求回来的响应Response转化为用户可以使用的Response(支持gzip压缩/解压)


okhttp
的缓存策略


为什么要使用缓存:

一个优点就是让客户端下一次的网络请求节省更多的时间,更快的展示数据

 
如何开启和使用缓存功能的呢?
直接使用OkHttpClientbuild来设置缓存:


new OkHttpClient.Builder()
        .cache(new Cache(new File("cache"),24*1024*1024))
        .build();

CacheInterceptor拦截到请求之后会调用到cache类的put方法,cache类实现了InternalCache接口,此接口定义了缓存的增删改查等方法,下面我们看一下如何将response缓存到cache里面

   
       @Nullable CacheRequest put(Response response) {
           
      
           //取到求方法
           String requestMethod = response.request().method();
           //判断该请求方法是否不符合存需求
           if (HttpMethod.invalidatesCache(response.request().method())) {
           
               remove(response.request());//移除               return null;
           }
           //非get方法不需要存,或者post不能被存,get求可以被CDN存,可以大大减少web服器的           if (!requestMethod.equals("GET")) {
               return null;
           }
           if (HttpHeaders.hasVaryAll(response)) {
               
           
               return null;}
           //开始           Entry entry = new Entry(response);
           DiskLruCache.Editor editor = null;//DiskLruCache存策略
           try {//把求url(经过转换MD5加密然后十六制)作key
               
               editor = cache.edit(key(response.request().url()));
               //真正的开始               entry.writeTo(editor);
               return new CacheRequestImpl(editor);
               
.....
           }
           

Cache类的put方法定义了如何通过传入的response潘墩是否需要需要缓存,以及创建Entry对象,把需要的一些属性  方法  地址  头部 信息  code 等写入缓存

我们知道OkHttp 是通过OkIo进行IO操作的,当然OkHttp的缓存自然也离不开OkIo的支持,接下来我们通过对entry.writeTo()方法的分析,来看一下如何使用OkIo进行缓存处理

     
   public void writeTo(DiskLruCache.Editor editor) throws IOException {
       
       //使用的是okio
       BufferedSink sink =Okio.buffer(editor.newSink(ENTRY_METADATA));
       //存的求地址
       sink.writeUtf8(url).writeByte('\n');
       //存的求方法
       sink.writeUtf8(requestMethod).writeByte('\n');
       
       //header行遍       for (int i = 0, size = varyHeaders.size(); i < size; i++) {
           
           sink.writeUtf8(varyHeaders.name(i)).writeUtf8(": ")
                   .writeUtf8(varyHeaders.value(i)).writeByte('\n');
           
       }
       //存http的响行StatusLine
       sink.writeUtf8(new StatusLine(protocol, code, message).toString())
               .writeByte('\n');
    ......
    }  
 if (isHttps()) {//判断是否是Https       //相的握手  等       sink.writeByte('\n');
     ......
    } //关   
 sink.close();
   
}
   

在上面的put方法里面我们看到了CacheRequestImpl对象

CacheRequestImpl实现了CacheRequest接口,主要暴漏给CacheInterceptorCacheInterceptor可以根据这个 CacheRequestImpl直接写入和更新缓存数据

put()方法总结:

1、首先判断缓存请求方法是否是get方法

2、如果符合缓存策略,那么创建一个Entry对象—>用于我们所要包装的缓存信息

3、最终使用DiskLruCache进行缓存

4、最后通过返回一个CacheRequestImpl对象,这个对象主要用于CacheInterceptor拦截器服务的

 

Cache类的作用,根据缓存策略创建entry类包裹response放入DiskLruCache缓存,然后在需要缓存的时候将根据request寻找到对应的entry并转成response返回

当然寻找缓存的操作封装在了get()请求里面:

get()方法总结:

1、根据请求url获取key(MD5解密)

2、通过key值获取 DiskLruCache.Snapshot(缓存快照)

3、通过获取到的这个snapshot获取到Source,最终创建entry对象

4、通过entrysnapshot(缓存快照)获取缓存的response并返回

 


前面总结了Cachegetput方法,HTTP的缓存的工作是通过CacheInterceptor拦截器来完成的


OkHttp
源码解读-->CacheInterceptor拦截器

如果当前未使用网络  并且缓存不可以使用,通过构建者模式创建一个Response响应  抛出504错误

  如果有缓存 但是不能使用网络  直接返回缓存结果

这是在进行网络请求之前所做的事情,当网络请求完成,得到下一个拦截器返回的response之后,判断response的响应码是否是HTTP_NOT_MODIFIED = 304   (未改变)是则从缓存中读取数据

此拦截器负责主要负责打开服务器之间的TCP链接,正式开启okhttp的网络请求

@Override public Response intercept(Chain chain) {
    
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    //StreamAllocation    StreamAllocation streamAllocation = realChain.streamAllocation();
    
    boolean doExtensiveHealthChecks = !request.method().equals("GET");

    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);

    RealConnection connection = streamAllocation.connection();
    
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
    
}

从代码上来看该拦截器的主要功能都交给了StreamAllocation处理,且这个类是从拦截器链对象(RealInterceptorChain对象)上获取的
  我们知道
RealInterceptorChain是在RealCallgetResponseWithInterceptorChain方法初始化的,但是去追踪源码的时候我们发现在那里StreamAllocation并没有被创建出来

由浅入深了解OkHttp五:OkHttp各拦截器作用介绍

其实在之前我们就了解过拦截器的原理,在整个拦截链条上所有的拦截器都在一定程度上共享了一份环境Chain(创建下一个chain的时候使用自身参数进行初始化)

因此实际上StreamAllocation是在第一个拦截器RetryAndFollowUpInterceptor里面进行创建的


那么这个StreamAllocation到底是神马玩意?

StreamAllocation用来建立http请求所需要的所有网络组件,在RetryAndFollowUpInterceptor 重试连接器中 只是初始化了 但是没有使用,它只是把创建好的StreamAllocation进行了向下传递   最终由ConnectInterceptor拦截器获取和使用

这里再次验证了我们之前的说法ChainOkHttp的拦截器链的环境,在拦截器链上所有的拦截器都可以对环境进行特定的处理,也就是改变chain中任务或者其他组件的状态

StreamAllocation的构造器里面有两个重要的参数:

1.使用了Okhttp的连接池ConnectionPool

2.通过url创建了一个Address对象。


Okhttp连接池简单说明:

Okhttp内部的连接池实现类为ConnectionPool,该类持有一个ArrayDeque队列作为缓存池,该队列里的元素为RealConnection(通过这个名字应该不难猜出是用来干嘛的)


RealConnection 负责实际的网络io传输的类

总结:

1、ConnectInterceptor从责任链的环境chain中取出StreamAllocation对象,通过StreamAllocation.newStream()方法获取HttpCodec对象,这个HttpCodec对象用于编码request和解码response,并且对不同http协议(http1.1http/2)的请求和响应做处理

2、newStream方法主要做的工作:

1)从缓冲池ConnectionPool获取一个RealConnection对象,如果缓冲池里面没有就创建一个RealConnection对象并且放入缓冲池中,具体的说是放入ConnectionPoolArrayDeque队列中。

2)获取RealConnection对象后并调用其connect打开Socket链接

3、将刚才创建好的用于网络IORealConnection对象,以及对于与服务器交互最为关键的HttpCodec等对象传递给后面的拦截


这个类大体上做了如下几个工作:

1StreamAllocationconnection能复用就复用之

2、如果connection不能复用,则从连接池中获取RealConnection,获取成功则返回,从连接池中获取RealConnection的方法调用了两次 ,第一次没有传Route,第二次传了

3,如果连接池里没有则创建一个RealConnection对象,并放入连接池中

4.最终调用RealConnectionconnect方法打开一个socket链接(此处暂且说结论,至于为何断定是socket链接,篇幅有限另外结合别的知识点另开博文说明)


终于到了OkHttp最后一个拦截器CallServerInterceptor


OkHttp源码解读—>CallServerInterceptor拦截器

此拦截器的主要功能是:
向服务器发送请求,并最终返回Response对象供客户端使用

该拦截器的拦截方法首先获取了httpCodec对象,该对象的主要功能是对不同http协议(http1.1http/2)的请求和响应做处理,该对象的初始化是在ConnectInterceporintercept里面

Okhttp的提供了两种HttpCodec的实现类,如果使用了http2协议则返回Http2Codec,否则返回Http1Codec!并且设置了超时时间,本篇就以Http1Codec对象来进行分析


我们知道Http发送网络请求前两个步骤是:

1、建立TCP链接

2、客户端向web服务器发送请求命令:

形如GET /login/login.jsp?username=android&password=123 HTTP/1.1的信息


OkhttpConnectInterceptor负责第一个步骤,那么第二个步骤是如何实现的呢?

答案就是httpcodecwriteRequestHeaders方法,在这个方法里面我们可以发现Okhttp是通过OkIOSink对象(该对象可以看做SocketOutputStream对象)来向服务器发送请求的

我们知道HTTP支持post,delete,get,put等方法,而postput等方法是需要请求体的(在Okhttp中用RequestBody来表示)。所以接着writeRequestHeaders之后Okhttp对请求体也做了响应的处理

通过上面的代码可以发现OkhttpExpect头部也做了支持,上面代码对客户端是否使用该头部做了判断

100 continue”的作用就是:客户端有一个RequestBody(比如post或者PUT方法)要发给服务器,但是客户端希望在发送RequestBody之前查看服务器是否接受这个body,服务端在接受到这个请求后必须进行响应。客户端通过Expect首部来发送这个消息,当然如果客户端没有实体发送,就不应该发送100 continue 首部,因为这样会使服务器误以为客户端有body要发送。所以okhttp在发送这个之前要permitsRequestBody来判断。当然常规的get请求是不会走这个方法的

ConnectInterceptor
执行完请求之后接着做的就是读取服务器响应的数据,构建response.builder对象
主要做了两个工作:

1、调用HttpCodecreadResponseHeaders方法读取服务器响应的数据,构建Response.Builder对象(以Hppt1Codec

2、通过ResopnseBuilder对象来最终创建Response对象并返回。主要是调用Http1Codec对象的openResponseBody方法,此方法将Socket的输入流InputStream对象交给OkIoSource对象,然后封装成RealResponseBody(该类是ResponseBody的子类)作为Responsebody

(在本篇博文中只需简单的将Sink作为Socket的输出流,Source作为Socket的输入流看待即可)


到此为止
CallServerInterceptor简单分析完毕,总结下主要做了如下工作:
 1、获取HttpCodec对象,对Http1.1或者http/2不同协议的http请求进行处理
 2、发送http请求数据,构建Resposne.Builder对象,然后构建Response并返回