1、概论
在上一篇文章里面,我们详细的剖析了HTTP协议的缓存机制。但那主要是从服务器端进行分析的,这有助于我们理解HTTP的缓存机制,并为我们用好OkHttp3这一客户端的封装库提供更为清晰的思路。知其原理,才能事半功倍。如果对于HTTP协议的缓存机制还不是很不清楚,可以去看下上一篇博客HTTP协议进阶之缓存
本篇文章主要从客户端的缓存控制出发,探讨如何利用OkHttp3是如何对缓存进行控制的。
2、请求报文的Cache-Control
在写代码之前,我们先通过下表大概回顾一下HTTP请求报文中与缓存相关的首部字段Cache-Control中的值都有哪些,它们的作用分别都是什么,它们的详细解释在上一篇博客HTTP协议进阶之缓存中:
指令 | 目的 |
---|---|
max-stale=<s> | 在<s>时间段内,文档不会过期。该指令放松了缓存的规则 |
min-fresh=<s> | 至少在未来的<s>秒内文档要保持新鲜。这使缓存规则更加严格了 |
max-age=<s> | 缓存无法返回缓存时间长于<s>秒的文档。这条指令会使规则更加严格,除非同时还发送了max-stale指令,在这种情况下,使用期可能会超过其过期时间。 |
no-cache | 除非资源进行了再验证,否则这个客户端不会接受已缓存的资源 |
no-store | 缓存应该尽快从存储器中删除文档的所有痕迹,因为其中可能会包含敏感信息 |
only-if-cached | 只有当缓存中有副本存在时,客户端才会获取一份副本 |
注:参见《HTTP权威指南》第194页,表中没有包括兼容性的Progma: no-cache
3、OkHttp3缓存简单使用
OkHttp3中关于缓存的类,我们使用的最多的是:Cache类和ControlCache类,其中前者用于指定缓存的地址和大小,后者用于对缓存进行各种控制,ControlCache又有其构建者类Builder。
先利用Cache类定义缓存地址和缓存最大尺寸:
long maxCacheSize = 100 * 1024 * 1024;
Cache cache = new Cache(
new File("E:/Soft_Develop/iMooc/NetworkFrameDesign/Test"),
maxCacheSize);
然后将其注入到OkHttpClient实例中:
OkHttpClient client = new OkHttpClient.Builder().cache(cache).build();
接着,构建我们的请求报文Request,我们使用凤凰网首页的网址,因为凤凰网首页的服务器支持缓存:
Request request = new Request.Builder()
.url("http://www.ifeng.com")
.build();
为了验证缓存是有效的,我们进行两次拉取,分别使用Response类的networkResponse()方法和cacheResponse()方法来查看从网络或缓存中读取到的内容情况:
Response response = client.newCall(request).execute();
response.body().close();
// String body2 = response2.body().string();
System.out.println("network response = " + response.networkResponse());
System.out.println("cache response = " + response.cacheResponse());
System.out.println("--------------------");
Response response2 = client.newCall(request).execute();
// String body2 = response2.body().string();
response.body().close();
System.out.println("network response2 = " + response2.networkResponse());
System.out.println("cache response2 = " + response2.cacheResponse());
控制台输出的响应如下:
由结果可知,由于在第一次请求时,缓存地址中还没有响应缓存,因此是从网络拉取数据,而第二次因为缓存地址中已有数据,因此是从缓存中拉取数据。
需要注意的是,在执行完请求之后,Response的Body需要关闭(Body的string()方法内嵌了关闭功能),否则缓存将不会如期生效,比如我们将其中关闭主体的两行去掉,那么结果将不会从缓存中读取:
我们到缓存地址中查看缓存文件,发现有三个:
我们查看d0dae*.0这个文件可:
它的内容我们是熟悉的,它里面包含了几个与缓存相关的字段,该文件所包含的就是缓存中存储的响应头。而主体就是对应的d0dae*.1这个文件了
journal文件这里暂且不管。
4、OkHttp3控制缓存
上一节说到,OkHttp3控制缓存的类为CacheControl。这一小节具体讲讲怎么使用该类。
要对缓存进行控制,我们需要在创建Request实例的时候就为其注入相应的缓存控制机制。这个注入是通过Request的cacheControl(CacheControl )方法实现的。例如,加入我不想存储缓存,而是直接从服务器拉取,并且不保存缓存。要实现这样的功能,HTTP是通过首部字段Cache-Control: no-store实现的。使用OkHttp3我们只需要在构造Request实例时按如下方式增加一行代码:
Request request = new Request.Builder()
.url("http://www.ifeng.com")
.cacheControl(new CacheControl.Builder().noStore().build())
.build();
浏览CacheControl.Builder类的方法,我们发现和第2节的表格高度对应:
针对这些方法,OkHttp3 API也给了我们这样两条建议:
(1)强制从网络获取资源
- 如果想要跳过缓存直接从网络中获取资源,可以通过noCache()方法;
- 如果需要每次请求都进行再验证环节,如果验证通过还是使用缓存,那么可以使用maxAge(0, TimeUnit.SECONDS)来构建CacheControl。
(2)强制从缓存获取网络资源
- 可以使用onlyIfCached()方法,使用该方法,如果缓存中没有时,将返回504 Unsatisfiable Request;
- 也可以采用maxStale(365, TimeUnit.DAYS),这样我们就使用了一个很长的缓存放松时间。
5、no-cache, no-store以及max-age=0辨析
上一节,我们使用了no-cache, no-store和max-age=0来强制和服务器进行沟通,但他们之间是有区别的,尤其是no-cache和max-age=0之间,更是令我费解了好一阵子。这里我们就来辨析他们的不同,好在这里我们通过OkHttp3可以很自由的变动我们的请求首部字段,也更容易观察现象。
按照上一章的理解(我参照的是《HTTP权威指南》和RFC7234),no-cache的意思是,在每次请求缓存时需要经过再验证。而OkHttp3给我们的建议中却说no-cache是直接从服务器拉取数据,这样便和再验证机制没什么关系了。
我原先来理解是,no-cache的作用和max-age=0的作用是一样的。但是事实验证不是如此。
我们通过如下实验来说明他们究竟说明的是什么?
5.1、no-cache和max-age=0的区别
首先,我们创建两个URL一样的请求,但第二个请求我们使用no-cache,代码如下:
Request request = new Request.Builder()
.url("http://www.ifeng.com")
.build();
Response response = client.newCall(request).execute();
response.body().close();
// String body2 = response2.body().string();
System.out.println("network response = " + response.networkResponse());
System.out.println("cache response = " + response.cacheResponse());
System.out.println("--------------------");
Request request2 = new Request.Builder()
.url("http://www.ifeng.com")
.cacheControl(new CacheControl.Builder().noCache().build())
.build();
Response response2 = client.newCall(request2).execute();
// String body2 = response2.body().string();
response.body().close();
System.out.println("network response2 = " + response2.networkResponse());
System.out.println("cache response2 = " + response2.cacheResponse());
结果为:
可以看出,第二次也是直接从网络读取,而不会从缓存中读取。如果将第二个请求中的noCache()换成maxAge(0, TimeUnit.SECONDS)。结果为:
可以清楚的看到,这次在第二次请求中,再验证机制起作用了,从服务器返回了304 Not Modified,然后再向缓存发起请求,并从响应返回了副本。
由此可见no-cache和max-age=0区别在于,前者直接从服务器拉取数据,后者使用了再验证机制。
RFC 2616中的一段话为这一结论带来了理论基础:
用通俗的话来说,max-age=0的功能是刷新而no-cache的功能是重新加载。使用chrome浏览器打开www.ifeng.com,然后利用开发者工具来抓取报文。当我们按F5时,请求报文部分如下:
而当我们按CTRL+F5时,请求报文部分如下:
因此可以得出结论max-age=0等于F5(刷新),no-cache等于CTRL+F5(重载)。
5.2、no-cache和no-store的区别
我们只使用一个Request请求,首先给它指定缓存控制为no-cache:
Request request = new Request.Builder()
.url("http://www.ifeng.com")
.cacheControl(new CacheControl.Builder().noCache().build())
.build();
Response response = client.newCall(request).execute();
response.body().close();
// String body2 = response2.body().string();
System.out.println("network response = " + response.networkResponse());
System.out.println("cache response = " + response.cacheResponse());
System.out.println("--------------------");
查看缓存地址中产生了缓存文件,但是当把noCache()换成noStore()之后没有产生缓存文件。
由此可见,max-age=0和no-cache的主要区别在于是否进行再验证。而no-cache和no-store的区别在于是否会缓存副本。
6、总结
本文主要从应用层面探讨了OkHttp3使用缓存时的基本用法,还有一些比较弄混的问题。但总体说来,我还是没有太弄清楚RFC 7243和《HTTP权威指南》中关于请求报文中的no-cache中的解释为什么会和再验证有关。如果有人看到这篇文章,并且知道为什么,还请指点一二,万分感激!
参考资料
《HTTP权威指南》
RFC 2616