1. 前言
之前以為只有靜態(tài)資源,瀏覽器才會(huì)使用到緩存庆揪。但最近在做 api 流量優(yōu)化時(shí)拔第,發(fā)現(xiàn) API 請(qǐng)求也會(huì)有緩存來減少服務(wù)器端的帶寬壓力的拌喉。
其中發(fā)生作用的就是 ETag,也是這里重點(diǎn)講述的內(nèi)容押框。順便通過該知識(shí)點(diǎn)岔绸,把 HTTP Cache 的知識(shí)點(diǎn)也過了一下。
2. Browser & Server 通訊
瀏覽器與服務(wù)器通訊,其中 request & response 的內(nèi)容分別包含了:
Browser Request
瀏覽器請(qǐng)求信息盒揉,一般分為以下部分:
- General
主要包含了 URL晋被、HTTP Method,如:
Request URL: https://www.baidu.com/img/bd_logo1.png?where=super
Request Method: GET
- Headers
定義了信息來源刚盈、瀏覽器支持羡洛、緩存策略等。這里的信息一般是給 WEB 應(yīng)用服務(wù)器解析的藕漱。
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7
Cache-Control: no-cache # 這是因?yàn)槭褂昧藶g覽器強(qiáng)刷欲侮,所以 no-cache
Connection: keep-alive
Cookie: PSTM=1545632734;省略...
Host: www.baidu.com
Referer: https://www.baidu.com/
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
- Parameters
Headers 更多是通訊標(biāo)準(zhǔn)定義的信息,自定義的信息內(nèi)容(類似投遞的信封內(nèi)的信息)一般放在 Parameters肋联。這信息更多是給程序解析的威蕉。
where=super
Server Response
服務(wù)器收到了瀏覽器請(qǐng)求,處理然后響應(yīng)橄仍,一般分為以下部分:
- General
Status Code: 200 OK
- Headers
一般包含信息的類型韧涨、大小、緩存策略侮繁、WEB應(yīng)用服務(wù)器等信息虑粥。
Accept-Ranges: bytes
Cache-Control: max-age=315360000
Connection: Keep-Alive
Content-Length: 7877
Content-Type: image/png
Date: Mon, 15 Jul 2019 06:50:14 GMT
Etag: "1ec5-502264e2ae4c0"
Expires: Thu, 12 Jul 2029 06:50:14 GMT
Last-Modified: Wed, 03 Sep 2014 10:00:27 GMT
Server: Apache
- Body
響應(yīng)回復(fù)的信息內(nèi)容。
HTTP 通訊過程中宪哩,瀏覽器 與 服務(wù)器主要是解讀和處理 headers 部分娩贷,Request parameters & Response body 主要是服務(wù)于具體業(yè)務(wù)部分,由程序解讀斋射。
是否應(yīng)用 cache育勺,也是通過 headers 的信息解析確定。
3. Cache
Cache 臺(tái)灣翻譯為 快取罗岖,內(nèi)地叫 緩存涧至。
通過 cache,可以節(jié)省流量桑包、時(shí)間等南蓬,減少資源的消耗。
舉例子哑了,一個(gè)電子商城赘方,首頁有上百張圖 和 API請(qǐng)求,如果沒有緩存弱左,每次進(jìn)來都需要重新加載所有內(nèi)容窄陡,流量是相當(dāng)驚人的,會(huì)加重服務(wù)器的帶寬壓力同時(shí)也會(huì)導(dǎo)致用戶下載等待過久影響體驗(yàn)拆火。
3.1 Cache 如何發(fā)生作用跳夭?
瀏覽器通過檢測(cè)請(qǐng)求的資源沒有==過時(shí)== 或者 ==內(nèi)容變化==涂圆,則從 cache 中取 response body。
這里可能有些人會(huì)有誤區(qū)币叹,緩存策略是不是意味著不請(qǐng)求服務(wù)器润歉?其實(shí)并不是,是會(huì)根據(jù)上面寫的觸發(fā) cache 的原因而有不同颈抚。
a) 按資源的有效期判斷
如圖所示踩衩,有部分資源服務(wù)器第一次響應(yīng)時(shí)會(huì)返回 max-age 或者 expires 告知瀏覽器,該資源在該時(shí)間范圍內(nèi)不產(chǎn)生變化贩汉。瀏覽器收到該信息后驱富,下次請(qǐng)求時(shí),會(huì)先檢查 max-age 和 expires雾鬼,如果在有效時(shí)間內(nèi)萌朱,則直接從 cache 中讀取。
- 應(yīng)用場(chǎng)景:靜態(tài)資源策菜,如圖片晶疼、js、css又憨、html
- Status Code:200 OK (from memory cache)
- 緩存讀取方式:memory cache (有生命周期的)
b) 按資源的內(nèi)容判斷
當(dāng)資源不滿足 (a) 的緩存策略翠霍,無法從 memory cache 中獲取數(shù)據(jù)時(shí),并不意味著緩存策略失效蠢莺,還可以通過與服務(wù)器溝通來進(jìn)一步確定寒匙。(a) 中的活動(dòng)圖按(b)進(jìn)一步拓展為 (注意活動(dòng)圖的 Before max-age 與 Before expired date 不一定存在,response 存在時(shí)才需要判斷):
- 應(yīng)用場(chǎng)景:動(dòng)態(tài)API躏将、動(dòng)態(tài)頁面锄弱、過期的靜態(tài)資源
- Status Code: 304 Not modified (from disk cache)
- 緩存讀取方式:disk cache
3.2 影響 cache 的 headers 因素
上一章節(jié)已經(jīng)講述了 cache 生效的機(jī)制,這里進(jìn)一步對(duì)生效中判斷的因素展開講解祸憋。
為了進(jìn)行學(xué)習(xí)会宪,特意找了一個(gè) API 進(jìn)行個(gè)例分析(去掉了一些非相關(guān)的項(xiàng)):
第一次請(qǐng)求:
General:
Request URL: https://cd3.lcola.cn/backend/charge_stations
Request Method: GET
Status Code: 200 OK
Referrer Policy: no-referrer-when-downgrade
Response Headers:
Cache-Control: max-age=0, private, must-revalidate
Date: Mon, 08 Jul 2019 09:05:48 GMT
# 請(qǐng)求有返回 Entity Tag
ETag: W/"25d6a32c560a93be6f4f3ba69f6353a6"
Request Headers:
Cache-Control: no-cache
Connection: keep-alive
Pragma: no-cache
第二次請(qǐng)求:
General:
Request URL: https://cd3.lcola.cn/backend/charge_stations
Request Method: GET
# 第二次狀態(tài)碼不同
Status Code: 304 Not Modified
Referrer Policy: no-referrer-when-downgrade
Response Headers:
Cache-Control: max-age=0, private, must-revalidate
Date: Mon, 08 Jul 2019 09:06:06 GMT
# Entity Tag 不變
ETag: W/"25d6a32c560a93be6f4f3ba69f6353a6"
Request Headers:
# 請(qǐng)求的頭部信息,有對(duì)于 ETag 的判斷
If-None-Match: W/"25d6a32c560a93be6f4f3ba69f6353a6"
通過上面的可以看到核心關(guān)鍵的 Response ETag 與 Request If-None-Match蚯窥。
開始來看看 瀏覽器 與 服務(wù)器如何通過 request 與 response 的 Pragma, Cache-Control, Etag, Last-Modified, Expires 進(jìn)行緩存機(jī)制掸鹅。
a) Expires
Response Headers:
Expires: Wed, 21 Oct 2017 07:28:00 GMT
Cache 的到期時(shí)間(具體的日期與時(shí)間),由服務(wù)器端定義拦赠。
瀏覽器收到 response header 后巍沙,會(huì)將資源存儲(chǔ)起來,然后等到下一次訪問同一請(qǐng)求時(shí)荷鼠,會(huì)檢查【當(dāng)前時(shí)間】是否超過這個(gè)【Expires】句携。如果沒有超過,則瀏覽器不發(fā)請(qǐng)求允乐,直接從 disk cache 直接取务甥,Status code 會(huì)標(biāo)記為:Status code 200 (from disk cache)牡辽。
?? 瀏覽器對(duì)該時(shí)間的判斷會(huì)依賴于客戶端,如果把時(shí)間改為 2999年敞临,那么這個(gè)請(qǐng)求的緩存機(jī)制就失效了。
b) Cache-Control max-age
Response Headers:
Cache-Control: max-age=30
Cache 失效的時(shí)間(秒)麸澜,通過計(jì)算下一次請(qǐng)求與第一次請(qǐng)求的的相隔時(shí)間挺尿,確定是否使用 cache。
可以有效解決 Expires 的客戶端時(shí)間依賴問題炊邦。
當(dāng)前 google 的首頁圖片就是通過 expires 與 max-age 進(jìn)行設(shè)置 cache 策略:
General:
Request URL: https://www.google.com.hk/images/nav_logo299.webp
Request Method: GET
Status Code: 200 (from disk cache)
Response Headers:
cache-control: private, max-age=31536000
date: Thu, 30 May 2019 17:11:47 GMT
expires: Thu, 30 May 2019 17:11:47 GMT
last-modified: Tue, 23 Apr 2019 01:00:00 GMT
那么相同同時(shí)設(shè)置编矾,并且假設(shè) max-age 與 expires 的失效時(shí)間不統(tǒng)一,那么以哪個(gè)為準(zhǔn)馁害?
根據(jù)RFC2616的定義:
If a response includes both an Expires header and a max-age directive, the max-age directive overrides the Expires header, even if the Expires header is more restrictive
max-age 的設(shè)置會(huì)覆蓋 Expires窄俏,同時(shí)設(shè)置時(shí),生效的只有 max-age碘菜。
c) Last-Modified 與 If-Modified-Since
a & b 在“有效時(shí)間的緩存策略”判斷中發(fā)揮作用凹蜈,c & d 講到的會(huì)在“內(nèi)容變化的緩存策略”判斷中發(fā)揮作用。
Response Headers:
Last-Modified: 2017-01-01 13:00:00
Cache-Control: max-age=31536000
Server 端通過 response 返回該資源的修改時(shí)間忍啸。
那么下次訪問該請(qǐng)求時(shí)仰坦,瀏覽器會(huì)通過 request If-Modified-Since,將第一次訪問的時(shí)間 與 Last-Modified 對(duì)比计雌,如果沒有更新悄晃,服務(wù)器會(huì)回復(fù) ==Status code: 304 (Not Modified)==,客戶端會(huì)繼續(xù)使用該資源凿滤。
Request Headers:
If-Modified-Since: Fri, 05 Jul 2019 02:14:23 GMT
Last-Modified 一般會(huì)結(jié)合 ETag 一起使用妈橄,確保是同一資源沒有被修改。
ETag 與 If-None-Match
對(duì)于靜態(tài)資源翁脆,可以通過 Last-Modified 來識(shí)別圖片是否被修改眷蚓,但是對(duì)于API請(qǐng)求,內(nèi)容基本都是根據(jù)請(qǐng)求參數(shù)動(dòng)態(tài)生成鹃祖,那么 Last-Modified 肯定是不適用溪椎。
動(dòng)態(tài)API請(qǐng)求,假設(shè)內(nèi)容相同恬口,是否可以應(yīng)用 cache 機(jī)制校读?
答案是:Yes。主要的實(shí)現(xiàn)是依賴于 reponse 的 ETag祖能。
Response Headers:
ETag: W/"1d6f97d3adad0e99400af20a1421a974"
ETag 是 Entity Tag 的簡寫歉秫,是一個(gè)資源版本的唯一標(biāo)識(shí),類似于一個(gè)資源的對(duì)應(yīng)的 hash code养铸。
如果一個(gè) response 有 ETag雁芙,那么瀏覽器下一次訪問該資源時(shí)轧膘,會(huì)在請(qǐng)求的頭部加上 If-None-Match,如果 Server Response 返回的 ETag 一樣兔甘,那么表示內(nèi)容沒有變化谎碍,返回 ==Status code: 304 (Not Modified)==。瀏覽器就通過從 cache 中重新獲取數(shù)據(jù)洞焙。
Request Headers:
If-None-Match: W/"a5759fbb890a0d32c4de53eea2264c03"
Strong v/s Weak ETags
"543b39c23d8d34c232b457297d38ad99" – Strong ETag
W/"543b39c23d8d34c232b457297d38ad99" – Weak ETag
ETag 分為 Strong 與 Weak
Strong ETag indicates that resource content is same for response body and the response headers.
Weak ETag indicates that the two representations are semantically equivalent. It compares only the response body.
Rails 4 默認(rèn)使用的是 Strong ETag, Rails5 默認(rèn)使用的是 Weak ETag蟆淀。
4. 案例講解
如何不使用 cache?
如果某些機(jī)密的信息澡匪,不希望留在客戶端熔任,可以 response 設(shè)置:
Response Headers:
Cache-Control: no-store
上面是指完全不適用快照,每次都需要重新像服務(wù)器請(qǐng)求唁情。
而 ==Cache-Control: no-cache== 還是會(huì)請(qǐng)求服務(wù)器疑苔,只有當(dāng)檢查到頁面信息變化了,才不獲取緩存甸鸟。等價(jià)于設(shè)置 ==max-age=0==
可以看到文章頭部的例子中惦费,強(qiáng)刷時(shí)瀏覽器會(huì)給請(qǐng)求加上 ==Cache-Control: no-cache==。
如何解決頁面緩存導(dǎo)致的外部資源引用不更新哀墓?
頁面的基本 html 內(nèi)容不變趁餐,但是里面的 css/js/image 的信息變化了(名字沒有改變),這時(shí)候很容易因?yàn)轫撁婢彺娑鴮?dǎo)致了頁面上沒有應(yīng)用到最新的 css/js/image篮绰。
為了解決這種情況后雷,css/js/image url 加上更新的時(shí)間戳作為參數(shù),這樣當(dāng)他們變化時(shí)頁面 response 也會(huì)重新生成 ETag吠各,從而重新加載所有信息臀突。
或者像 webpack 打包一樣,資源重新生成一個(gè)帶 hash 的名字贾漏。
獲取資源時(shí)候学,Http method 該用 get 還是 post?
如果參照 restful 標(biāo)準(zhǔn)纵散,獲取資源時(shí) http method 應(yīng)使用 get梳码,但是有時(shí)候考慮到參數(shù)過長問題,部分時(shí)候會(huì)使用 post伍掀。正常情況掰茶,只是 定義不同,獲取方式不同蜜笤,實(shí)際對(duì)緩存策略也是有影響的濒蒋。
==獲取資源能用 GET 別用 POST==
因?yàn)?GET API 請(qǐng)求有 cache 機(jī)制支持,所以這是為什么請(qǐng)求不建議使用 POST。
POST 請(qǐng)求不提交 If-None-Match沪伙,所以 cache 不生效瓮顽。另外參數(shù)不在URL,每次請(qǐng)求因?yàn)閰?shù)變化導(dǎo)致的 response 內(nèi)容變化围橡,ETag 也變化暖混,cache 基本作廢。
5. 參考資料
《循序漸進(jìn)理解 HTTP Cache 機(jī)制》
《W3C》
《Developers google - HTTP caching》
《plantuml》
附上面用到的 plantuml 源碼:
@startuml
title HTTP Communication \n planttext.com
start
:Browser request;
note left
HTTP url & method
end note
:Check last response;
if (a. Before max-age?) then (yes)
:Get response \n__from memory cache__;
elseif (b. Before expired date?) then (yes)
:Get response \n__from memory cache__;
else (no)
:Communicate with server;
if (c. If-Modified-Since?) then (no)
:Response with\n status code 304 & no data;
:Get response\n__from disk cache__;
elseif (d. If-None-Match?) then (no)
:Response with\n status code 304 & no data;
:Get response\n__from disk cache__;
else (yes)
:Response with\n status code 200 & data;
endif
endif
:Render;
stop
@enduml