前面章节讲解了OkHttp的模型架构和网络请求各个参数的意义。本章节主要讲解OkHttp真正进行网络请求的部分,也就是拦截器。
OkHttp默认实现的有,重试与重定向拦截器、桥接拦截器、缓存拦截器、连接拦截器和请求服务拦截器。他们用责任链设计模式有效的组合成一个整体,完成复杂的网络请求工作。
一、责任链设计模式
责任链模式是常见的23种设计模式其中之一。在责任链模式里,多个对象由每一个对象对其下家的引用而连接起来形成一个链条。请求在这个链上传递,链上的每个节点都可以对请求进行处理。这种设计模式可以方便的在链条上增加和删除处理节点,使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
二、五大拦截器
前面在讲OkHttp网络请求框架时,“getResponseWithInterceptorChain”这个方法,最后会返回请求的结果。这个方法首先将各个拦截器串在责任链上,然后开启责任链网络请求的工作在各拦截器进行流转。
接下来我们详细看一下这几个拦截器的内部是如何工作。
2.1重试与重定向拦截器 RetryAndFollowUpInterceptor
这个拦截器,首先前置工作做链接准备,寻找一条可用的链接。
然后开始中置工作,开启后面的拦截器,进行处理。
最后开启后置工作,根据中置工作后面拦截器的返回结果判断是否进行重试、重定向或者直接将结果返回。
重试
通过某条连接线路失败,需要重试。执行continue重新开始一轮请求。
此时如果!recover则抛出异常,不在进行重试。recover中判断的情形主要有:设置了禁止重试开关、该请求体不能二次发起、返回的异常不能发起重试、连接已不可达。
重定向
请求返回结果没有连接异常,检查服务器返回结果是否发生服务器地址迁移,需要进行重定向。前面网络相关知识介绍过重定向的返回有301和302分别表示资源永久迁移和临时迁移。
这里就是根据返回的标记,以及返回重定向后网址。进行发起重定向。
followUpRequest()这个方法,就是从响应中提取重定向相关信息。
如果有发生资源重定向,并且重定向次数没有超过限制--20次,则修改请求内容,重新开始新一轮请求。
2.2桥接拦截器 BridgeInterceptor
这个拦截器主要是Okhttp帮助我们生成HTTP请求头的内容。下面分别具体看一下:
设置内容类型、内容长度和分块传输。OkHttp根据我们传入参数解析内容类型、自动计算内容的长度,如果是分块传输则设置分块传输指令并填入当前块长度。
设置主机名。根据传入的url自动解析出主机名,并自动填入请求头。
设置长连接。
设置gzip压缩。为了减少数据传输的体积,okHttp会默认帮我们设置“使用gZip”压缩。在该拦截器的后置阶段会自动帮我们解gZip压缩。
自动带入cookie,如果有的话。
设置代理
完成上面请求头设置后,进入中置阶段,交给下一个拦截器进行处理。
后置阶段,拿到后面拦截器返回的结果,对结果进行gZip解压,再交给前一个拦截器。
桥接拦截器,就是帮我们做在HTTP请求时,需要我们做,但是做起来会很麻烦的事----生成请求头。从而大大的减轻我们的工作量。
2.3缓存拦截器 CacheInterceptor
进行Get请求时,在真正向服务器发起请求之前,先判断是否命中缓存。缓存如果命中,则直接使用缓存的内容,不用再向服务器发起请求。从而减小网络访问的开销。
这个拦截器执行的步骤是:
1、从缓存中获取请求对应的缓存数据;
2、创建缓存策略CacheStrategy,根据策略类的成员变量networkRequest和cacheResponse判断使用缓存的方式。它们的组合如下:
networkRequest |
cacheResponse |
策略 |
Null |
NotNull |
直接使用缓存 |
NotNull |
Null |
不使用缓存,向服务器发起请求 |
Null |
Null |
直接返回504(Gateway Timeout) |
NotNull |
NotNull |
发起请求,得到响应为304则使用缓存 |
3、若需要向服务器发起请求,则流转到下一个拦截器进行处理;
4、返回304表示服务器无修改,可以使用缓存。否则,使用网络响应的内容,并缓存本次响应数据。
2.4连接拦截器 ConnectInterceptor
这个拦截器,是建立一个连接用来发起请求。和前面拦截器不同,建立拦截之后,直接交给下一个拦截器进行处理,它没有后置处理的工作。
这里有一个很重要的过程,就是对连接的复用。当没有可以复用的连接时,才会创建连接。
获取连接的调用流程:ConnectInterceptor:intercept() -> RealCall:initExchange()->ExchangeFinder:find()->findHealthyConnection()->findConnection()
返回的codec就是编码解码器,根据HTTP不同版本返回不同的编码规则。
findConnection方法就是做的获取连接的工作。主看看一下这个方法:
第1、从连接池中查找。遍历连接池,判断每一个连接看是否有可用的。
这里判断的可用的方法,首先看是否支持多路服用,即是HTTP2协议。
在isEligible方法中判断是否可以复用:
判断是否连接超过限制,HTTP1.X一个连接只能处理一个请求,noNewExchanges表示不可以处理新的请求。
判断当前连接和目标请求是否指向相同,地址、端口、代理等都要一样
判断主机名是否一致。
下面这些是针对HTTP2的对于连接是否可以合并的判断。
以上都没有问题,则isEligible方法返回true,表示可以连接可以复用。进一步返回连接。如果此时无法获取则findConnection方法继续向下进行。
第2、判断是否可以多路复用。带入routes参数,再一次遍历连接池。
上一次只能得到HTTP1.X的复用连接,这里才能获得HTTP2的连接复用。
routes是通过RouteSelector获取的。只有routes不为空时,才会走到这里进行连接复用获取。
Route表示“路由”,它是由代理、ip地址和端口组成。代理有三种类型:DIRECT(没有代理,直连方式)、HTTP、SOCKS。一个域名可能有多个ip地址。到达一个域名有多种连接方式,经过相同类型代理到达指定域名的会被分到一个routerSelection,多个routerSection并入到RouteSelector中。RouteSelector中存储的是所有到达目标域名的路由。
举个例子,
域名 mydemo.com,有1.2.3.4:443 和5.6.7.8:443 两个地址。
现在有三个HTTP代理,分别是9.10.11.12,13.14.15.16,17.18.19.20
则到达该域名路由有8种,两种直连方式,6种代理方式。这些都会记录在RouteSelector中。
在isEligible方法中根据routes参数判断是否可以进行HTTP2多路复用。
routeMachesAny方法判断代理的方式为“直连”并且socket的地址相同。
第3、创建连接。
经过上面的方式,仍然不能获得可复用的连接,则开始主动创建连接。
第4、再次尝试从连接池获取
这个地方处理是为了做进一步的防范,避免出现一种极端的情况:两个新的请求同时开始,都是第一次尝试建立连接,并且都新建了连接。如果他们都是HTTP2方式,可以进行连接合并,则此时只需要保留一个即可。另一个可以释放掉。
注意到这一次从连接池获得,requireMultiplexed为true,它只会处理HTTP2的连接。不支持多路复用的方式都会被过滤掉。
如果此时可以从连接池中获取,则命中这种极端情况。则将此次新建的连接关闭,并复用连接池中的连接。
第5、保存新的连接到连接池
进行到这里则会使用新建的连接,并将该连接保存到连接池中。
获得连接后,基于这个连接建立HTTP编解码器codec,再将codec塞到exchange里面。就完成了它的工作。进入到下一个拦截器。
2.5请求服务拦截器 CallServerInterceptor
这个拦截器的工作就是向服务器发起请求并读取服务器的响应。
使用从上一个拦截器得到的HttpCodec发出请求。将请求头写入到缓存中。然后开始一系列的逻辑判断。直到调用flushRequest()方法才真正将数据发送给服务器。
permitsRequestBody判断是否带有请求体,即是否是POST请求方式。“Expect:100-continue”表示请求头在发送请求体之前想要和服务器协商是否接收客户端发送请求体。
此时,要先向服务器发起一次请求。如果服务器响应100,表示服务器愿意接收,才可以继续发送剩余的请求。
这里面有几种情况:
a、POST请求,带有“Expect:100-continue”标记,服务器可以接收请求体。客户端发送剩余请求体,再次读取响应;
b、POST方式,带有“Expect:100-continue”,服务器不允许接收请求体,标记连接不复用,并进行关闭连接;
c、POST方式,没有“Expect:100-continue”,直接发出请求体内容,等待并处理服务器响应;
d、POST方式,没有请求体,发出后等待服务器响应;
e、GET及其他方式,发出请求后等待服务器响应。
这里先不看WebSocket的方式。对响应数据进行一些处理。判断是否close,如果是,则需要关闭socket。判断204、205返回,这个表示没有响应体,如果此时contentlentth不为0,表示响应体有数据,则出现冲突,抛出异常。其他情况,直接将返回数据返回给上一拦截器处理。
然后会回到前面的拦截器,如根据响应内容判断是否重试重定向等,是否重新发起请求,或者将响应内容返回给客户端。
到此完成了OkHttp五大拦截器的学习。
2.6 自定义拦截器 networkInterceptors
这个拦截器一般做网络调试用,用户自定义,插在连接拦截器和请求服务拦截器之间。
三、总结
整个OkHttp的网络请求工作都是在这个五大拦截器中完成。他们分别负责不同的工作,如果流水线一样,经过五到工序,最终完成了完整的工作。
OkHttp的每个拦截器,会在将请求交给下一个拦截器之前做一些事情,在获得结果之后,再做一些事情。这个过程的请求是顺序的,而响应是逆序的。
当用户发起请求后,任务用分发器Dispatcher将请求包装好,交给拦截器。
重试重定向拦截器,主要在获取结果之后,判断是否需要重试和重定向,如果满足条件,则重启执行所有拦截器;
桥接拦截器,负责将HTTP协议必备请求头信息加入其中;
缓存拦截器,判断是否有可用缓存;
连接拦截器,负责复用已有连接或新建一个连接;
请求服务拦截器,负责与服务器进行通讯,并获取解析响应的数据。