阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680
本篇文章将继续从以下两个内容来解析OkHttp3源码:
- [HTTP重定向]
- [缓存的处理]
一、HTTP重定向
1.1 重定向
重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置(如:网页重定向、域名的重定向、路由选择的变化也是对数据报文经由路径的一种重定向)。 ----百度百科
1.2 原理
在 HTTP 协议中,重定向操作由服务器通过发送特殊的响应(即 redirects)而触发。HTTP 协议的重定向响应的状态码为 3xx 。浏览器在接收到重定向响应的时候,会采用该响应提供的新的 URL ,并立即进行加载;大多数情况下,除了会有一小部分性能损失之外,重定向操作对于用户来说是不可见的。
image不同类型的重定向映射可以划分为三个类别:永久重定向、临时重定向和特殊重定向。
1.3 永久重定向
这种重定向操作是永久性的。它表示原 URL 不应再被使用,而应该优先选用新的 URL。搜索引擎机器人会在遇到该状态码时触发更新操作,在其索引库中修改与该资源相关的 URL 。
注意了:浏览器自动重定向,不管代码怎么写都会自动重定向到第一次你永久重定向的URL,没办法,这时候你只能清楚浏览器缓存
编码 | 含义 | 处理方法 | 典型应用场景 |
---|---|---|---|
301 | Moved Permanently | GET 方法不会发生变更,其他方法有可能会变更为 GET 方法。 | 网站重构。 |
308 | Permanent Redirect | 方法和消息主体都不发生变化。 | 网站重构。with non-GET links/operations(?) |
1.4 临时重定向
有时候请求的资源无法从其标准地址访问,但是却可以从另外的地方访问。在这种情况下可以使用临时重定向。搜索引擎不会记录该新的、临时的链接。在创建、更新或者删除资源的时候,临时重定向也可以用于显示临时性的进度页面。
那么临时重定向就是解决自己傻乎乎的用永久重定向的问题。
编码 | 含义 | 处理方法 | 典型应用场景 |
---|---|---|---|
302 | Found | GET 方法不会发生变更,其他方法有可能会变更为 GET 方法。 | 由于不可预见的原因该页面暂不可用。在这种情况下,搜索引擎不会更新它们的链接。 |
303 | See Other | GET 方法不会发生变更,其他方法会变更为 GET 方法(消息主体会丢失)。 | 用于PUT 或 POST 请求完成之后进行页面跳转来防止由于页面刷新导致的操作的重复触发。 |
307 | Temporary Redirect | 方法和消息主体都不发生变化。 | 由于不可预见的原因该页面暂不可用。在这种情况下,搜索引擎不会更新它们的链接。当站点支持非 GET 方法的链接或操作的时候,该状态码优于 302 状态码。 |
1.5 特殊重定向
除了上述两种常见的重定向之外,还有两种特殊的重定向。304 (Not Modified,资源未被修改)会使页面跳转到本地缓存的版本当中(该缓存已过期(?)),而 300 (Multiple Choice,多项选择) 则是一种手工重定向:以 Web 页面形式呈现在浏览器中的消息主体包含了一个可能的重定向链接的列表,用户可以从中进行选择。
编码 | 含义 | 典型应用场景 |
---|---|---|
300 | Multiple Choice | 不会太多:所有的选项在消息主体的 HTML 页面中列出。也可以返回 200 OK 状态码。 |
304 | Not Modified | 缓存刷新:该状态码表示缓存值依然有效,可以使用。 |
二、缓存处理
2.1 缓存的优势
缓存的使用场景很多,通过它可以将数据通过一定的规则存储起来,再次请求数据的时候就可以快速从缓存中读取了,缓存有以下优势。
- 减少向服务器请求的次数,减轻服务器的负载。
- 加快了本地的响应速度,直接从缓存中取数据比从网络读取要快很多。
- 提供无网模式下的浏览体验,没有网络的情况下也能显示内容。
2.2 HTTP的缓存机制
HTTP本身提供了一套缓存相关的机制。这套机制定义了相关的字段和规则,用来客户端和服务端进行缓存相关的协商,如响应的数据是否需要缓存,缓存有效期,缓存是否有效,服务器端给出指示,而客户端则根据服务端的指示做具体的缓存更新和读取缓存工作。http缓存可以分为两类:
强制缓存
强制缓存,是直接向缓存数据库请求数据,如果找到了对应的缓存数据,并且是有效的,就直接返回缓存数据。如果没有找到或失效了,则向服务器请求数据,返回数据和缓存规则,同时将数据和缓存规则保存到缓存数据库中。
对比缓存
对比缓存,是先向缓存数据库获取缓存数据的标识,然后用该标识去服务器请求该标识对应的数据是否失效,如果没有失效,服务器会返回304未失效响应,则客户端使用该标识对应的缓存。如果失效了,服务器会返回最新的数据和缓存规则,客户端使用返回的最新数据,同时将数据和缓存规则保存到缓存数据库中。
强制缓存
强制缓存,在缓存数据未失效的情况下,可以直接使用缓存数据,有两个字段Expires和Cache-Control用于标明失效规则。
Expires
表示过期时间,由服务端返回。那么下次请求数据时,判断这个Expires过期时间是否已经过了,如果还没有到过期时间,则使用缓存,如果过了过期时间,则重新请求服务器的数据。Expires格式如下:
Expires: Sat, 11 Nov 2017 10:30:01 GMT
表示到期时间是2017年11月11日10点30分,在这个时间之前可以使用缓存,过了这个时间就要重新请求服务器数据了。
不过因为服务器和客户端的时间并不是同步的,用一个绝对时间作为过期的标记并不是很明智,所以HTTP1.1之后更多的是Cache-Control,它的控制更加灵活。
Cache-Control
表示缓存的控制,有服务端返回。它有以下几个取值:
public
表示数据内容都可以被储存起来,就连有密码保护的网页也储存,安全性很低
private
表示数据内容只能被储存到私有的cache,仅对某个用户有效,不能共享
no-cache
表示可以缓存,但是只有在跟WEB服务器验证了其有效后,才能返回给客户端,触发对比缓存
no-store
表示请求和响应都禁止被缓存,强制缓存,对比缓存都不会触发
max-age
表示返回数据的过期时间
默认情况下是private,也就是不能共享的。Cache-Control格式如下:
Cache-Control:public, max-age=31536000
表示可以被公共缓存,有效时间是1年,也就是说一年时间内,请求该数据时,直接使用缓存,而不用请求服务器了。
对比缓存
对比缓存,表示需要和服务端进行相关信息的对比,由服务器决定是使用缓存还是最新内容,如果服务器判定使用缓存,返回响应吗304,判定使用最新内容,则返回响应码200和最新数据。对比缓存的判定字段有两组:
ETag和If-None-Match
ETag表示资源的一种标识信息,用于标识某个资源,由服务端返回,优先级更高。格式如下:
Etag:"AFY10-6MddXmSerSiXP1ZTiU65VS"
表示该资源的标识是AFY10-6MddXmSerSiXP1ZTiU65VS
然后客户端再次请求时,加入字段If-None-Match,格式如下:
If-None-Match:"AFY10-6MddXmSerSiXP1ZTiU65VS"
服务端收到请求的该字段时(之前的Etag值),和资源的唯一标识进行对比,如果相同,说明没有改动,则返回状态码304,如果不同,说明资源被改过了,则返回状态码200和整个内容数据。
Last-Modified和If-Modified-Since
Last-Modified表示资源的最近修改时间,由服务端返回,优先级更低。格式如下:
Last-Modified: Sat, 11 Nov 2017 10:30:01 GMT
表示上次修改时间是2017年11月11日10点30分。
If-Modified-Since: Sat, 11 Nov 2017 10:30:01 GMT
客户端请求,表示我指定的这个2017年11月11日10点30分是不是你服务器最新的修改时间。
Last-Modified
由服务器返回,表示响应的数据最近修改的时间。
If-Modified-Since
由客户端请求,表示询问服务器这个时间是不是上次修改的时间。如果服务端该资源的修改时间小于等于If-Modified-Since指定的时间,说明资源没有改动,返回响应状态码304,可以使用缓存。如果服务端该资源的修改时间大于If-Modified-Since指定的时间,说明资源又有改动了,则返回响应状态码200和最新数据给客户端,客户端使用响应返回的最新数据。
Last-Modified字段的值(服务端返回的资源上次修改时间),常常被用于客户端下次请求时的If-Modified-Since字段中。
两种缓存的区别
强制缓存的情况下,如果缓存是有效的,则直接使用缓存,而对比缓存不管缓存是否有效,都需要先去和服务器对比是否有新的数据,没有新的数据才使用缓存数据。
两种缓存的使用情景
对于强制缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行对比缓存策略。
对于对比缓存,将缓存信息中的Etag和Last-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。
HTTP的缓存规则总结
HTTP的缓存规则是优先考虑强制缓存,然后考虑对比缓存。
- 首先判断强制缓存中的数据的是否在有效期内。如果在有效期,则直接使用缓存。如果过了有效期,则进入对比缓存。
- 在对比缓存过程中,判断ETag是否有变动,如果服务端返回没有变动,说明资源未改变,使用缓存。如果有变动,判断Last-Modified。
- 判断Last-Modified,如果服务端对比资源的上次修改时间没有变化,则使用缓存,否则重新请求服务端的数据,并作缓存工作。
2.3 Okhttp缓存相关类
Okhttp缓存相关的类有如下:
CacheControl(HTTP中的Cache-Control和Pragma缓存控制)
CacheControl是用于描述HTTP的Cache-Control和Pragma字段的类,用于指定缓存的规则。
CacheStrategy(缓存策略类)
CacheStrategy是用于判定使用缓存数据还是网络请求的决策类。
Cache(缓存类)
对外开放的缓存类,提供了缓存的增删改查接口。
InternalCache(内部缓存类)
对内使用的缓存类接口,没有具体实现,只是封装了Cache的使用。
DiskLruCache(文件化的LRU缓存类)
这是真正实现缓存功能的类,将数据存储在文件中,并使用LRU规则(由LinkedHashMap实现),控制对缓存文件的增删改查。
2.4 Okhttp缓存的启用
要开启使用Okhttp的缓存其实很简单,只需要给OkHttpClient对象设置一个Cache对象即可,创建一个Cache时指定缓存保存的目录和缓存最大的大小即可。
那么下面我们来看看Okhttp缓存执行的大概流程
2.5 Okhttp的缓存流程
Okhttp的缓存流程分为读取缓存和存储缓存两个过程,我们分别分析。
Okhttp读取缓存流程
读取使用缓存的流程从HttpEngine的sendRequest发送请求开始。
- 首先获取OkHttpClient的Cache缓存对象,就是之前创建OkHttpClient时设置的Cache。
- 然后传入Request请求到Cache的get方法去查找缓存响应数据Response。
- 构造一个缓存策略,传入Request请求和缓存响应Response,然后调用它的get方法去决策使用网络请求还是缓存响应。
- 策略判定之后,如果是使用缓存,则它的cacheResponse不为空,networkRequest为空,如果使用请求,则相反。然后再将策略给出的这两个值,继续处理。
- 如果使用请求,但是之前又找到了缓存响应,则要关闭缓存响应资源。
- 如果策略得出缓存响应为空,网络请求也为空,则返回请求不合理的响应。(比如强制使用缓存,但是找不到缓存的情况下)
- 如果请求为空,缓存不为空,也就是使用缓存的情况,则使用缓存响应来构造返回的响应数据。
- 最后就是只使用网络请求的情况,走网络请求路线。
总的来说就是,先查找是否有可用的Cache,然后通过Cache找到请求对应的缓存,然后将请求和缓存交给缓存策略去判断使用请求还是缓存,得出结果后,自己再判断使用缓存还是请求,如果使用缓存,用缓存构造响应直接返回,如果使用请求,那么开始网络请求流程。
接下来我们分析
- Cache是如何获取缓存的。
- 缓存策略是如何判断的。
Cache获取缓存
从Cache的get方法开始。它按以下步骤进行。
- 计算request对应的key值,md5加密请求url得到。
- 根据key值去DiskLruCache查找是否存在缓存内容。
- 存在缓存的话,创建缓存Entry实体。ENTRY_METADATA代表响应头信息,ENTRY_BODY代表响应体信息。
- 然后根据缓存Entry实体得到响应,其中包含了缓存的响应头和响应体信息。
- 匹配这个缓存响应和请求的信息是否匹配,不匹配的话要关闭资源,匹配的话返回。
如果存在缓存的话,在指定的缓存目录中,会有两个文件“****.0”和“****.1”,分别存储某个请求缓存的响应头和响应体信息。(“****”是url的md5加密值)对应的ENTRY_METADATA响应头和ENTRY_BODY响应体。缓存的读取其实是由DiskLruCache来读取的,DiskLruCache是支持Lru(最近最少访问)规则的用于磁盘存储的类,对应LruCache内存存储。它在存储的内容超过指定值之后,就会根据最近最少访问的规则,把最近最少访问的数据移除,以达到总大小不超过限制的目的。
接下来我们分析CacheStrategy缓存策略是怎么判定的。
CacheStrategy缓存策略
直接看CacheStrategy的get方法。缓存策略是由请求和缓存响应共同决定的。
- 如果缓存响应为空,则缓存策略为不使用缓存。
- 如果请求是https但是缓存响应没有握手信息,同上不使用缓存。
- 如果请求和缓存响应都是不可缓存的,同上不使用缓存。
- 如果请求是noCache,并且又包含If-Modified-Since或If-None-Match,同上不使用缓存。
- 然后计算请求有效时间是否符合响应的过期时间,如果响应在有效范围内,则缓存策略使用缓存。
- 否则创建一个新的有条件的请求,返回有条件的缓存策略。
- 如果判定的缓存策略的网络请求不为空,但是只使用缓存,则返回两者都为空的缓存策略。
接来下我们看看CacheControl类里有些什么。
CacheControl
可以发现,它就是用于描述响应的缓存控制信息。
然后我们再看看Okhttp存储缓存是怎么进行的。
Okhttp存储缓存流程
存储缓存的流程从HttpEngine的readResponse发送请求开始的。
可以看到这里先通过maybeCache写入了响应头信息,再通过cacheWritingResponse写入了响应体信息。我们再进去看Cache的put方法实现。
我们继续看Cache的writeTo方法,可以看到是写入一些响应头信息。
到这里Okhttp缓存的读取和存储流程我们就清楚了。可以说,缓存的使用策略基本都是按照HTTP的缓存定义来实现的,所以对HTTP缓存相关字段的理解是很重要的。然后关于DiskLruCache是如何管理缓存文件的,这个其实也很好理解,首先的原则就是按照LRU这种最近最少使用删除的原则,当总的大小超过限定大小后,删除最近最少使用的缓存文件,它的LRU算法是使用LinkedHashMap进行维护的,这样来保证,保留的缓存文件都是更常使用的。具体实现大家可以分析DiskLruCache和LinkedHashMap的实现原理。
参考 https://www.jianshu.com/p/00d281c226f6
https://www.jianshu.com/p/3f9128380cf7
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680