HTTP緩存深入實(shí)踐

網(wǎng)絡(luò)特有的延遲以及數(shù)據(jù)傳輸?shù)某杀荆萍s互聯(lián)網(wǎng)快速獲取Web資源家淤。為此异剥,HTTP協(xié)議引入緩存以空間換時間,使瀏覽器緩存和重用已獲取的資源絮重,解決網(wǎng)絡(luò)延遲和數(shù)據(jù)傳輸成本高的問題冤寿,提升訪問體驗(yàn)错妖。隨著HTTP協(xié)議1.0->1.1->2的演進(jìn),關(guān)于緩存控制的部分有一些變化疚沐。但我覺著暂氯,在開發(fā)Web服務(wù)時,首先要關(guān)注請求頭If-Modified-Since/響應(yīng)頭Last-Modified亮蛔、請求頭If-None-Match/響應(yīng)頭ETag痴施、響應(yīng)頭Cache-Control。因?yàn)檫@三個HTTP頭可以滿足你的大部分HTTP緩存需求究流,并且辣吃,當(dāng)今絕大多數(shù)瀏覽器都支持這三個HTTP頭。我們所要做的芬探,就是確保每個服務(wù)器響應(yīng)都提供正確的HTTP頭指令神得,以指導(dǎo)瀏覽器何時緩存響應(yīng)以及緩存多久。

HTTP緩存在哪兒偷仿?

圖1 HTTP緩存在哪兒

如圖1所示HTTP緩存存在于瀏覽器和Web代理中哩簿。當(dāng)然在服務(wù)器內(nèi)部,也存在著各種緩存酝静,比如本地緩存GuavaCache/Ehcache节榜,分布式緩存Memcached/Redis等,但這不是本文要討論的HTTP緩存别智。所謂的HTTP緩存控制宗苍,就是一種協(xié)議約定,通過設(shè)置不同的響應(yīng)頭Cache-Control來控制瀏覽器和Web代理對緩存的使用策略薄榛,通過設(shè)置請求頭If-Modified-Since/響應(yīng)頭Last-Modified讳窟、請求頭If-None-Match/響應(yīng)頭ETag,來對緩存的有效性進(jìn)行驗(yàn)證敞恋。

本文對請求頭If-Modified-Since/響應(yīng)頭Last-Modified丽啡、請求頭If-None-Match/響應(yīng)頭ETag、響應(yīng)頭Cache-Control進(jìn)行實(shí)踐耳舅,分析這三種HTTP緩存頭在瀏覽器和服務(wù)器的表現(xiàn)碌上。實(shí)踐方式,服務(wù)端實(shí)現(xiàn)使用Spring boot+Java浦徊,前端實(shí)現(xiàn)使用HTML馏予,訪問方式結(jié)合瀏覽器和nc命令行交叉使用,其中nc執(zhí)行如下命令盔性,在端口8080建立連接霞丧,可以設(shè)置請求頭,模擬瀏覽器發(fā)送輸入HTTP請求冕香。

nc 127.0.0.1 8080

注意:由于前端和后端打印時間對象類不一樣蛹尝,將可能打印GMT或者CST時間后豫,GMT比CST晚8小時。

請求頭If-Modified-Since/響應(yīng)頭Last-Modified

圖2 請求頭If-Modified-Since/響應(yīng)頭Last-Modified交互時序圖

如圖2所示突那,請求頭If-Modified-Since/響應(yīng)頭Last-Modified交互過程如下:

  1. 如圖2紅色部分所示挫酿,瀏覽器第一次向服務(wù)器請求資源時,服務(wù)器返回狀態(tài)200和資源內(nèi)容愕难,同時在響應(yīng)頭Last-Modified字段標(biāo)記該資源內(nèi)容在服務(wù)器最后被修改時間早龟,格式類似:Last-Modified: Wed, 02 Aug 2017 10:34:50 GMT,供下一次瀏覽器在請求頭If-Modified-Since使用猫缭;
  2. 如圖2紫色部分所示葱弟,瀏覽器第二次向服務(wù)器請求該資源時,根據(jù)HTTP協(xié)議規(guī)定猜丹,瀏覽器在請求頭If-Modified-Since寫入上次服務(wù)器返回的響應(yīng)頭Last-Modified值芝加,向服務(wù)器詢問該時間之后資源是否被修改過,格式類似:If-Modified-Since: Wed, 02 Aug 2017 10:34:50 GMT射窒;
  3. 如圖2綠色部分所示藏杖,當(dāng)服務(wù)器資源文件有修改過,重新返回資源和狀態(tài)碼200給瀏覽器轮洋,同時在響應(yīng)頭Last-Modified字段標(biāo)記該資源內(nèi)容在服務(wù)器最后被修改時間制市;
  4. 如圖2藍(lán)色部分所示,當(dāng)服務(wù)器資源內(nèi)容沒有被修改過弊予,只給瀏覽器返回狀態(tài)碼304(Not Modified),不返回請求資源开财,瀏覽器根據(jù)304使用本地緩存汉柒,從而節(jié)省帶寬,提高網(wǎng)頁響應(yīng)速度责鳍。

下面舉例實(shí)踐碾褂。
后端Java代碼如下,其中CacheControlController#getResourceLastModified()方法標(biāo)注資源最后修改時間和用來設(shè)置Last-Modified: Wed, 02 Aug 2017 19:30:20 GMT历葛。

package com.demo.web.http;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.ServletWebRequest;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

@RequestMapping("http")
public class CacheControlController {
    @RequestMapping("/cache-control/last-modified")
    public String lastModified(ServletWebRequest request) {
        if (request.checkNotModified(getResourceLastModified())) {
            //it will return 304 with empty body
            return null;
        }
        return "http/cache-control/last-modified";
    }

    private static long getResourceLastModified() {
        ZonedDateTime zdt = ZonedDateTime.of(LocalDateTime.of(2017, 8, 2,
                19, 30, 20),
                ZoneId.of("GMT"));
        return zdt.toInstant().toEpochMilli();
    }
}

前端HTML代碼:

<!DOCTYPE HTML>
<html>
<head>
    <title>HTTP cache control Demo</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script src="http://code.jquery.com/jquery-1.11.3.min.js" type="text/javascript"></script>
</head>
<body>
<h3>實(shí)踐Cache-Control last-modified</h3>
<a href="last-modified">刷新頁面</a>
</body>
</html>

如圖3綠色方框所示正塌,當(dāng)請求頭If-Modified-Since: Wed, 02 Aug 2017 19:30:15 GMT早于資源最后修改時間Wed, 02 Aug 2017 19:30:20 GMT,表示W(wǎng)eb資源在上一次返回后有修改過恤溶,所以服務(wù)器重新返回資源內(nèi)容乓诽,返回狀態(tài)碼200,并在響應(yīng)頭返回資源最后修改時間Last-Modified: Wed, 02 Aug 2017 19:30:20 GMT咒程。

圖3 服務(wù)器資源已更新(Last-Modified)

如圖4紅色和綠色方框所示鸠天,當(dāng)請求頭If-Modified-Since: Wed, 02 Aug 2017 19:30:15 GMT等于或晚于資源最后修改時間Wed, 02 Aug 2017 19:30:20 GMT,表示資源在上一次返回后沒有修改過帐姻,所以服務(wù)器只返回狀態(tài)碼304稠集,不返回資源內(nèi)容奶段,瀏覽器使用本地緩存資源。

圖4 服務(wù)器資源無更新(Last-Modified)

請求頭If-None-Match/響應(yīng)頭ETag

ETag全稱Entity Tag剥纷,用來標(biāo)識一個Web資源痹籍,反映資源內(nèi)容的變化。在具體的實(shí)現(xiàn)中晦鞋,ETag可以是Web資源的hash值蹲缠,也可以是一個服務(wù)器內(nèi)部維護(hù)的版本號。

圖5 請求頭If-None-Match/響應(yīng)頭ETag交互時序圖

如圖5所示鳖宾,請求頭If-None-Match/響應(yīng)頭ETag交互過程如下:

  1. 如圖5紅色部分所示吼砂,瀏覽器第一次向服務(wù)器請求資源時,服務(wù)器返回狀態(tài)碼200和資源內(nèi)容鼎文,同時在響應(yīng)頭ETag字段標(biāo)識該資源內(nèi)容渔肩,格式類似:ETag: "50b1a1d4f885c61:df4",供下一次瀏覽器在請求頭If-None-Match使用拇惋;
  2. 如圖5紫色部分所示周偎,瀏覽器第二次向服務(wù)器請求該資源時,根據(jù)HTTP協(xié)議規(guī)定撑帖,瀏覽器在請求頭If-None-Match寫入上次服務(wù)器返回的響應(yīng)頭ETag值蓉坎,與服務(wù)器的資源標(biāo)識對比后判斷資源是否被修改過,格式類似:If-None-Match: "50b1a1d4f885c61:df4"胡嘿;
  3. 如圖5綠色部分所示蛉艾,當(dāng)服務(wù)器資源內(nèi)容有修改過,給瀏覽器重新返回資源和狀態(tài)碼200衷敌,同時在響應(yīng)頭ETag字段標(biāo)記該資源文件的實(shí)體值勿侯;
  4. 如圖5藍(lán)色部分所示,當(dāng)服務(wù)器資源內(nèi)容沒有修改過缴罗,只給給瀏覽器返回狀態(tài)碼304(Not Modified)助琐,不返回請求資源,瀏覽器根據(jù)304使用本地緩存面氓,節(jié)省帶寬兵钮,提高網(wǎng)頁訪問速度。

下面舉例實(shí)踐舌界。
后端Java代碼如下掘譬,CacheControlController#getETag()方法表明資源的唯一標(biāo)識token和設(shè)置ETag值為:"etag_version"

package com.demo.web.http;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.ServletWebRequest;

@RequestMapping("http")
public class CacheControlController {
    @RequestMapping("/cache-control/etag")
    public String etag(ServletWebRequest request) {
        if (request.checkNotModified(getETag())) {
            //it will return 304 with empty body
            return null;
        }
        return "http/cache-control/etag";
    }

    private static String getETag() {
        return "etag_version";
    }
}

前端HTML代碼:

<!DOCTYPE HTML>
<html>
<head>
    <title>HTTP cache control Demo</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script src="http://code.jquery.com/jquery-1.11.3.min.js" type="text/javascript"></script>
</head>
<body>
<h3>實(shí)踐Cache-Control ETag</h3>
<a href="etag">刷新頁面</a>
</body>
</html>

如圖6禀横,當(dāng)服務(wù)器資源唯一標(biāo)識token:"etag_version"不等于If-None-Match: "etag_version_new"屁药,表示資源在上一次返回后有改變,所以服務(wù)器重新返回資源內(nèi)容和狀態(tài)碼200。

圖6 服務(wù)器資源已更新(ETag)

如圖7酿箭,當(dāng)服務(wù)器資源唯一標(biāo)識token:"etag_version"等于If-None-Match: "etag_version"复亏,表示資源在上一次返回后無變化,所以服務(wù)器不返回資源文件缭嫡,只返回狀態(tài)碼304缔御,瀏覽器使用本地緩存資源。

圖7 服務(wù)器資源無更新(ETag)

請求頭If-Modified-Since/響應(yīng)頭Last-Modified妇蛀、請求頭If-None-Match/響應(yīng)頭ETag總結(jié)

Last-Modified和Etags如何幫助提高性能?
服務(wù)端首先產(chǎn)生Last-Modified/Etag標(biāo)記耕突,并在HTTP響應(yīng)頭返回給客戶端,服務(wù)端稍后使用它們判斷頁面是否已經(jīng)被修改评架【熳拢客戶端通過請求頭If-Modified-Since/If-None-Match將Last-Modified/Etag標(biāo)記傳回給服務(wù)器,要求服務(wù)器驗(yàn)證其(客戶端)緩存纵诞。
過程如下:
1. 客戶端請求一個頁面(A)上祈。
2. 服務(wù)器返回頁面A,并在A加上響應(yīng)頭Last-Modified/ETag浙芙。
3. 客戶端展現(xiàn)該頁面登刺,并將頁面連同Last-Modified/ETag一起緩存。
4. 客戶再次請求頁面A嗡呼,并將上次請求時服務(wù)器返回的Last-Modified/ETag作為請求頭If-Modified-Since/If-None-Match一起傳遞給服務(wù)器纸俭。
5. 服務(wù)器檢查If-Modified-Since/If-None-Match,判斷出該頁面自上次客戶端請求之后還未被修改南窗,直接返回狀態(tài)碼304和一個空的響應(yīng)體揍很。

Cache-Control

每個Web資源都可以通過HTTP響應(yīng)頭Cache-Control定義自己的緩存策略,Cache-Control控制誰在什么條件下可以緩存響應(yīng)以及可以緩存多久万伤。 最快的請求是不必與服務(wù)器進(jìn)行通信:通過響應(yīng)的本地副本女轿,我們可以避免所有的網(wǎng)絡(luò)延遲以及數(shù)據(jù)傳輸?shù)某杀尽榇撕爵妫琀TTP規(guī)范允許服務(wù)器返回一系列不同的Cache-Control指令,控制瀏覽器或者其他中繼代理緩存如何緩存某個響應(yīng)以及緩存多長時間傅寡。
Cache-Control響應(yīng)頭在 HTTP/1.1規(guī)范中定義放妈,當(dāng)前的所有瀏覽器都支持 Cache-Control,取代了之前用來定義響應(yīng)緩存策略的頭(例如 Expires)荐操。
Cache-Control常用的指令包括如下四個芜抒,下面將對這四個指令舉例實(shí)踐。

  • max-age
  • no-cache
  • no-store
  • public/private

max-age

max-age指令指定從當(dāng)前請求開始托启,允許獲取的響應(yīng)被重用的最長時間(單位為秒)宅倒。例如Cache-Control:max-age=30表示響應(yīng)可以再緩存和重用 30 秒。注意屯耸,在max-age指定的時間之內(nèi)拐迁,瀏覽器不會向服務(wù)器發(fā)送任何請求蹭劈,包括驗(yàn)證緩存是否有效的請求,也就是說线召,如果在這段時間之內(nèi)铺韧,服務(wù)器上的資源發(fā)生了變化,那么瀏覽器將不能得到通知缓淹,而使用老版本的資源哈打。所以在設(shè)置緩存時間的長度時,需要慎重讯壶。
后端Java代碼如下料仗,響應(yīng)頭設(shè)置:Cache-Control:max-age=30

package com.demo.web.http;

import org.springframework.http.CacheControl;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@RequestMapping("http")
public class CacheControlController {
    @RequestMapping("/cache-control/max-age")
    public String maxAge(HttpServletResponse response) {
        System.out.println("服務(wù)端訪問時間:" + new Date());
        String headerValue = CacheControl.maxAge(30, TimeUnit.SECONDS).getHeaderValue();
        response.addHeader("Cache-Control", headerValue);
        return "http/cache-control/max-age";
    }
}

前端HTML代碼:

<!DOCTYPE HTML>
<html>
<head>
    <title>HTTP cache control Demo</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script src="http://code.jquery.com/jquery-1.11.3.min.js" type="text/javascript"></script>
</head>
<body>
<h3>實(shí)踐Cache-Control max-age=10</h3>
<p id="time"></p>
<a href="max-age">刷新頁面</a>
<script>
    $("#time").text("頁面訪問時間:" + new Date());
</script>
</body>
</html>

如圖8所示伏蚊,瀏覽器第一次請求http://127.0.0.1:8080/http/cache-control/max-age立轧,服務(wù)器返回響應(yīng)頭:

Cache-Control:max-age=30
Date:Wed, 02 Aug 2017 13:09:46 GMT

瀏覽器渲染頁面訪問時間:Wed Aug 02 2017 21:09:46 GMT+0800(CST)
服務(wù)端打印日志:服務(wù)端訪問時間:Wed Aug 02 21:09:46 CST 2017丙挽。

圖8 第一次訪問max-age

如圖9所示肺孵,瀏覽器第二次請求http://127.0.0.1:8080/http/cache-control/max-age,響應(yīng)頭與圖8一樣颜阐,不一樣的是頁面訪問時間:Wed Aug 02 2017 21:10:03 GMT+0800(CST)平窘,Status Code多了(from disk cache),并且服務(wù)端沒有打印日志凳怨,這些區(qū)別表明在30s內(nèi)第二次請求沒有與服務(wù)器通信瑰艘,瀏覽器直接使用本地緩存的副本渲染頁面。

圖9 第二次訪問max-age
200 OK (from cache) 與 304 Not Modified 區(qū)別

200 OK (from cache) 是瀏覽器沒有跟服務(wù)器確認(rèn)肤舞,直接使用瀏覽器本地緩存紫新;而 304 Not Modified 是瀏覽器和服務(wù)器多確認(rèn)一次緩存有效性,再用瀏覽器的緩存李剖。200(from cache) 速度最快芒率,因?yàn)椴恍枰L問遠(yuǎn)程服務(wù)器,直接使用本地緩存篙顺。304 的過程是先請求服務(wù)器,偶芍,然后服務(wù)器告訴瀏覽器這個資源沒變,瀏覽器再使用本地緩存德玫。

如圖10所示匪蟀,當(dāng)超過30s(max-age=30)后重新請求http://127.0.0.1:8080/http/cache-control/max-age,服務(wù)器返回響應(yīng)頭:

Cache-Control:max-age=30
Date:Wed, 02 Aug 2017 13:10:31 GMT

瀏覽器渲染頁面訪問時間:Wed Aug 02 2017 21:10:32 GMT+0800(CST)宰僧。服務(wù)端打印日志:服務(wù)端訪問時間:Wed Aug 02 21:10:31 CST 2017材彪。與圖8和圖9比較,30s后瀏覽器不再使用本地緩存的副本渲染頁面,重新請求服務(wù)器資源段化。

圖10 第三次訪問max-age

max-age=0

max-age=0是表示響應(yīng)可以再緩存和重用 0 秒嘁捷,max-age指令的特殊情況,下面先舉例實(shí)踐穗泵,再給出結(jié)論普气。
后端Java代碼如下,響應(yīng)頭設(shè)置:Cache-Control:max-age=0佃延。

package com.demo.web.http;

import org.springframework.http.CacheControl;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@RequestMapping("http")
public class CacheControlController {
    @RequestMapping("/cache-control/max-age0")
    public String maxAge0(ServletWebRequest request, HttpServletResponse response) {
        System.out.println("服務(wù)端訪問時間:" + new Date());
        if (request.checkNotModified(getETag())) {
            //it will return 304 with empty body
            return null;
        }
        String headerValue = CacheControl.maxAge(0, TimeUnit.SECONDS).getHeaderValue();
        response.addHeader("Cache-Control", headerValue);
        return "http/cache-control/max-age0";
    }

    private static String getETag() {
        return "etag_version";
    }
}

前端HTML代碼:

<!DOCTYPE HTML>
<html>
<head>
    <title>HTTP cache control Demo</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script src="http://code.jquery.com/jquery-1.11.3.min.js" type="text/javascript"></script>
</head>
<body>
<h3>實(shí)踐Cache-Control max-age=0</h3>
<p id="time"></p>
<a href="max-age0">刷新頁面</a>
<script>
    $("#time").text("頁面訪問時間:" + new Date());
</script>
</body>
</html>

如圖11所示现诀,瀏覽器第一次請求http://127.0.0.1:8080/http/cache-control/max-age0,服務(wù)器返回響應(yīng)頭:

Cache-Control:max-age=0
Date:Thu Aug 03 12:45:51 GMT

瀏覽器渲染頁面訪問時間:Thu Aug 03 20:45:52 GMT+0800(CST)履肃。服務(wù)端打印日志:服務(wù)端訪問時間:Thu Aug 03 20:45:51 CST 2017仔沿。

圖11 第一次訪問max-age0

如圖12所示,瀏覽器第二次請求http://127.0.0.1:8080/http/cache-control/max-age0尺棋,服務(wù)器返回Status
Code 304 (Not Modified)封锉,響應(yīng)頭:

Cache-Control:max-age=0
Date:Thu Aug 03 12:46:09 GMT

瀏覽器渲染頁面訪問時間:Thu Aug 03 12:46:09 GMT+0800(CST)。服務(wù)端打印日志:服務(wù)端訪問時間:Thu Aug 03 20:46:09 CST 2017膘螟。
表明max-age=0情況下成福,響應(yīng)緩存和重用 0 秒,瀏覽器每次都會請求服務(wù)器獲取資源荆残。

圖12 第二次訪問max-age0
max-age結(jié)論

max-age>0 時奴艾,響應(yīng)緩存和重用指定秒數(shù),在指定秒數(shù)內(nèi)直接使用游覽器緩存的副本内斯。
max-age<=0 時杯瞻,向服務(wù)端發(fā)送HTTP請求確認(rèn)妖异,該資源是否有修改啊胶。有修改則返回200和最新資源肃拜;無修改則返回304,使用游覽器緩存的副本真朗。

no-cache

如果服務(wù)器在響應(yīng)頭中設(shè)置Cache-Control:no-cache此疹,那么瀏覽器在使用緩存的資源之前,必須先與服務(wù)器確認(rèn)上次返回的資源是否被更改遮婶,如果資源未被更改秀菱,直接使用瀏覽器緩存的副本,避免重新下載蹭睡。這個驗(yàn)證之前的響應(yīng)是否被修改,就是通過上面介紹的請求頭If-None-match和響應(yīng)頭ETag來實(shí)現(xiàn)的赶么。
注意肩豁,no-cache這個名字有一點(diǎn)誤導(dǎo)。設(shè)置no-cache之后,并不是說瀏覽器就不再緩存數(shù)據(jù)清钥,只是瀏覽器在使用緩存數(shù)據(jù)時琼锋,需要先確認(rèn)一下資源是否跟服務(wù)器還保持一致。如果設(shè)置了no-cache祟昭,而ETag的實(shí)現(xiàn)沒有反映出資源的變化缕坎,瀏覽器的緩存數(shù)據(jù)就不會更新,瀏覽器與服務(wù)器的每次請求連接反而降低性能篡悟。所以在服務(wù)器資源變化不頻繁情況下谜叹,設(shè)置no-cache對于性能反而有所下降。
后端Java代碼如下搬葬,響應(yīng)頭設(shè)置:Cache-Control:no-cache荷腊。

package com.demo.web.http;

import org.springframework.http.CacheControl;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;

@RequestMapping("http")
public class CacheControlController {
    @RequestMapping("/cache-control/no-cache")
    public String noCache(ServletWebRequest request, HttpServletResponse response) {
        System.out.println("服務(wù)端訪問時間:" +  new Date());
        if (request.checkNotModified(getETag())) {
            //it will return 304 with empty body
            return null;
        }

        String headerValue = CacheControl.noCache().getHeaderValue();
        response.addHeader("Cache-Control", headerValue);
        return "http/cache-control/no-cache";
    }

    private static String getETag() {
        return "etag_version";
    }
}

前端HTML代碼:

<!DOCTYPE HTML>
<html>
<head>
    <title>HTTP cache control Demo</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script src="http://code.jquery.com/jquery-1.11.3.min.js" type="text/javascript"></script>
</head>
<body>
<h3>實(shí)踐Cache-Control no-cache</h3>
<p id="time"></p>
<a href="no-cache">刷新頁面</a>
<script>
    $("#time").text("頁面訪問時間:" + new Date());
</script>
</body>
</html>

如圖13所示,結(jié)合請求頭If-None-Match/響應(yīng)頭ETag一節(jié)急凰,第一次請求http://127.0.0.1:8080/http/cache-control/no-cache女仰,服務(wù)器返回碼200,響應(yīng)頭:

Cache-Control:no-cache
ETag:"etag_version"

服務(wù)器日志打印如下:

服務(wù)端訪問時間:Thu Aug 03 20:06:11 CST 2017
圖13 第一次請求no-cache

如圖14所示抡锈,第二次請求http://127.0.0.1:8080/http/cache-control/no-cache疾忍,請求頭攜帶If-None-Match: "etag_version",服務(wù)器返回碼304床三,瀏覽器使用本地緩存一罩,響應(yīng)頭如下:

ETag:"etag_version"

服務(wù)器日志打印如下:

服務(wù)端訪問時間:Thu Aug 03 20:07:15 CST 2017
圖14 第二次請求no-cache

結(jié)合圖13和圖14的分析,結(jié)論:如果服務(wù)器在響應(yīng)頭設(shè)置Cache-Control:no-cache勿璃,那么瀏覽器在使用緩存的資源之前擒抛,必須先與服務(wù)器確認(rèn)上次返回的資源是否被更改,如果資源未被更改补疑,服務(wù)器返回304 Not Modified歧沪,瀏覽器使用本地緩存,避免重新下載資源莲组。

no-cache/max-age=0區(qū)別
相同之處

no-cache并不是表示無緩存诊胞,而是指使用緩存一定要先經(jīng)過驗(yàn)證;而max-age=0表示緩存到本地锹杈,只是在下次重新訪問該頁面時又會強(qiáng)制地從服務(wù)器驗(yàn)證資源撵孤。所以大部分情況下,這倆其實(shí)是一樣的竭望。

不同之處

當(dāng)點(diǎn)擊瀏覽器的前進(jìn)后退按鈕時邪码,被no-cache的資源會重新加載;而被設(shè)置成max-age的讀取則會從本地讀取資源咬清。當(dāng)然這也需要根據(jù)瀏覽器實(shí)現(xiàn)的情況來看闭专,某些瀏覽器如IE9之前的IE奴潘,并沒有遵循h(huán)ttp協(xié)議,直接統(tǒng)一了這兩個字段的行為為no-cache影钉。

手動刷新頁面(F5)画髓,瀏覽器會直接認(rèn)為緩存已經(jīng)過期(可能緩存還沒有過期),在請求中加上字段:Cache-Control:max-age=0平委,發(fā)包向服務(wù)器查詢是否有文件是否有更新奈虾。

強(qiáng)制刷新頁面(Ctrl+F5),瀏覽器會直接忽略本地的緩存(有緩存也會認(rèn)為本地沒有緩存)廉赔,在請求中加上字段:Cache-Control:no-cache(或 Pragma:no-cache)肉微,發(fā)包向服務(wù)重新拉取文件。

當(dāng)然昂勉,各個瀏覽器對于刷新和強(qiáng)制刷新的實(shí)現(xiàn)方式也有一些區(qū)別浪册。

no-cache/must-revalidate區(qū)別

no-cache: 告訴瀏覽器、緩存服務(wù)器岗照,不管本地副本是否過期村象,使用資源副本前,一定要到源服務(wù)器進(jìn)行副本有效性校驗(yàn)攒至。
must-revalidate:告訴瀏覽器厚者、緩存服務(wù)器,本地副本過期前迫吐,可以使用本地副本库菲;本地副本一旦過期,必須去源服務(wù)器進(jìn)行有效性校驗(yàn)志膀。

no-store

如果服務(wù)器在響應(yīng)頭設(shè)置Cache-Control:no-store熙宇,根據(jù)HTTP協(xié)議約定,瀏覽器和任何中繼的Web代理溉浙,都不會存儲這次響應(yīng)的數(shù)據(jù)烫止。當(dāng)下次請求該資源時,瀏覽器只能重新請求服務(wù)器戳稽,重新從服務(wù)器讀取資源馆蠕。
后端Java代碼如下,響應(yīng)頭設(shè)置:Cache-Control:no-store惊奇。

package com.demo.web.http;

import org.springframework.http.CacheControl;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;

@RequestMapping("http")
public class CacheControlController {
    @RequestMapping("/cache-control/no-store")
    public String noStore(ServletWebRequest request, HttpServletResponse response) {
        System.out.println("服務(wù)端訪問時間:" + new Date());
        if (request.checkNotModified(getETag())) {
            //it will return 304 with empty body
            return null;
        }

        String headerValue = CacheControl.noStore().getHeaderValue();
        response.addHeader("Cache-Control", headerValue);
        return "http/cache-control/no-store";
    }

    private static String getETag() {
        return "etag_version";
    }
}

前端HTML代碼:

<!DOCTYPE HTML>
<html>
<head>
    <title>HTTP cache control Demo</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script src="http://code.jquery.com/jquery-1.11.3.min.js" type="text/javascript"></script>
</head>
<body>
<h3>實(shí)踐Cache-Control no-store</h3>
<p id="time"></p>
<a href="no-store">刷新頁面</a>
<script>
    $("#time").text("頁面訪問時間:" + new Date());
</script>
</body>
</html>

如圖12所示互躬,結(jié)合請求頭If-None-Match/響應(yīng)頭ETag一節(jié),第一次請求http://127.0.0.1:8080/http/cache-control/no-store颂郎,服務(wù)器返回碼200吼渡,響應(yīng)頭:

Cache-Control:no-store
ETag:"etag_version"

服務(wù)器日志打印如下:

服務(wù)端訪問時間:Thu Aug 03 20:08:23 CST 2017
圖15 第一次請求no-store

如圖16所示,第二次請求http://127.0.0.1:8080/http/cache-control/no-store乓序,瀏覽器請求頭不再攜帶If-None-Match: "etag_version诞吱,服務(wù)器返回碼200舟奠,響應(yīng)頭如下:

Cache-Control:no-store
ETag:"etag_version"

服務(wù)器日志打印如下,說明瀏覽器每次重新請求服務(wù)器獲取資源房维,而不會緩存資源副本。

服務(wù)端訪問時間:Thu Aug 03 20:09:03 CST 2017
圖16 第二次請求no-store

結(jié)合圖15和圖16的分析抬纸,結(jié)論:如果服務(wù)器在響應(yīng)中設(shè)置Cache-Control:no-store咙俩,那么瀏覽器和任何中繼的Web代理,都不會存儲這次響應(yīng)的數(shù)據(jù)湿故。當(dāng)下次請求該資源時阿趁,瀏覽器只能重新請求服務(wù)器,重新從服務(wù)器讀取資源坛猪。

public和private

如果設(shè)置Cache-Control:public脖阵,表示該響應(yīng)可以在瀏覽器或者任何中繼的Web代理中緩存,public是默認(rèn)值墅茉,即Cache-Control:max-age=60等同于Cache-Control:public, max-age=60命黔。
在服務(wù)器設(shè)置Cache-Control:private, max-age=60,表示只有用戶的瀏覽器可以緩存private響應(yīng)就斤,不允許任何中繼Web代理對其進(jìn)行緩存悍募。例如,瀏覽器可以緩存包含用戶私人信息的 HTML 網(wǎng)頁洋机,但是 CDN 不能緩存坠宴。
鑒于單機(jī)無法模擬中繼Web代理,本小結(jié)暫不實(shí)踐绷旗。

Cache-Control策略優(yōu)先級

如下圖17所示喜鼓,所謂的HTTP緩存控制,就是一種協(xié)議約定衔肢,通過設(shè)置不同的響應(yīng)頭Cache-Control來控制瀏覽器和Web代理對緩存的使用策略庄岖,通過設(shè)置請求頭If-Modified-Since/響應(yīng)頭Last-Modified、請求頭If-None-Match/響應(yīng)頭ETag膀懈,來對緩存的有效性進(jìn)行驗(yàn)證顿锰。
注意,當(dāng)Cache-Control: no-store時启搂,瀏覽器不會在請求頭帶ETag硼控。

圖17 Cache-Control策略優(yōu)先級

清除緩存方法

最常用的辦法就是修改文件的版本號,或者生成隨機(jī)文件名胳赌,或者改變文件的最后修改時間牢撼。如果你只是在本地測試,想手動清楚緩存的話疑苫,可以使用圖18的用戶操作熏版,模擬上面的HTTP緩存實(shí)踐纷责。

[圖片上傳失敗...(image-20774a-1525271449369)]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市撼短,隨后出現(xiàn)的幾起案子再膳,更是在濱河造成了極大的恐慌,老刑警劉巖曲横,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喂柒,死亡現(xiàn)場離奇詭異,居然都是意外死亡禾嫉,警方通過查閱死者的電腦和手機(jī)灾杰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來熙参,“玉大人艳吠,你說我怎么就攤上這事∧跻” “怎么了昭娩?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長弄屡。 經(jīng)常有香客問我题禀,道長,這世上最難降的妖魔是什么膀捷? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任迈嘹,我火速辦了婚禮,結(jié)果婚禮上全庸,老公的妹妹穿的比我還像新娘秀仲。我一直安慰自己,他們只是感情好壶笼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布神僵。 她就那樣靜靜地躺著,像睡著了一般覆劈。 火紅的嫁衣襯著肌膚如雪保礼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天责语,我揣著相機(jī)與錄音炮障,去河邊找鬼。 笑死坤候,一個胖子當(dāng)著我的面吹牛胁赢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播白筹,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼智末,長吁一口氣:“原來是場噩夢啊……” “哼谅摄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起系馆,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤送漠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后由蘑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體螺男,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年纵穿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奢人。...
    茶點(diǎn)故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡谓媒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出何乎,到底是詐尸還是另有隱情句惯,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布支救,位于F島的核電站抢野,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏各墨。R本人自食惡果不足惜指孤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贬堵。 院中可真熱鬧恃轩,春花似錦、人聲如沸黎做。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蒸殿。三九已至筷厘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宏所,已是汗流浹背酥艳。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留楣铁,地道東北人玖雁。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像盖腕,于是被迫代替她去往敵國和親赫冬。 傳聞我的和親對象是個殘疾皇子浓镜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容