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 = * * ;
Cache cache = new Cache(
new File("E:/Soft_Develop/iMooc/NetworkFrameDesign/Test"),
maxCacheSize);
然後将其注入到OkHttpClient執行個體中:
接着,建構我們的請求封包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