瀏覽器之HTTP緩存的那些事

緩存是提高用戶訪問速度,節省帶寬,減輕服務器壓力的必經之道。javascript

下面都是針對的Http 1.1來講明,HTTP緩存都是針對瀏覽器客戶端,其餘第三方客戶端不考慮。

什麼是瀏覽器緩存

簡單來講,瀏覽器緩存就是把一個已經請求過的Web資源(如html,圖片,js)拷貝一份副本儲存在瀏覽器中。緩存會根據進來的請求保存輸出內容的副本。當下一個請求來到的時候,若是是相同的URL,緩存會根據緩存機制決定是直接使用副本響應訪問請求,仍是向源服務器再次發送請求(固然還有304的狀況)。css

緩存是根據url來處理的,只要url不同就是新的資源。html

瀏覽器HTTP執行機制

瀏覽器對於請求資源, 擁有一系列成熟的緩存策略。前端

只要有相應的緩存響應頭(要求緩存),瀏覽器客戶端都會對資源緩存一份,固然緩存響應頭也有優先級的。java

緩存模式

瀏覽器緩存能夠分爲兩種模式,強緩存協商緩存jquery

  • 強緩存(無HTTP請求,無需協商)nginx

    直接讀取本地緩存,無需跟服務端發送請求確認,http返回狀態碼是200(from memory cache或者from disk cache ,不一樣瀏覽器返回的信息不一致的)。git

    對應的Http header有:github

    • Cache-Control
    • Expires
  • 協商緩存(有HTTP請求,需協商)web

    瀏覽器雖然發現了本地有該資源的緩存,可是不肯定是不是最新的,因而想服務器詢問,若服務器認爲瀏覽器的緩存版本還可用,那麼便會返回304(Not Modified) http狀態碼。

    對應的Http header有:

    • Last-Modified
    • ETag

流程圖

流程圖只考慮了200和304的狀態碼,其餘異常狀態碼不考慮。
瀏覽器緩存流程

緩存相關的Http Header

Http Header包括請求頭和響應頭,http1.1會向前兼用的,會兼容http1.0的Http header,瀏覽器還有web服務通常都會考慮進去。

Http Header 描述
Cache-Control 指定緩存機制,優先級最高
Pragma http1.0字段,已廢棄,爲了兼容通常使用no-cache
Expires http1.0字段,指定緩存的過時時間(爲了兼容)
Last-Modified http1.0字段,資源最後一次的修改時間
ETag 惟一標識請求資源的字符串,會覆蓋Last-Modified

Cache-Control

瀏覽器緩存裏, Cache-Control是金字塔頂尖的規則, 它藐視一切其餘設置, 只要其餘設置與其抵觸, 一概覆蓋之.

不只如此, 它仍是一個複合規則, 包含多種值,同時在請求頭和響應頭均可設置(基本均可以)。

下面列舉了經常使用的Cache-Control用法。

Cache-Control 描述
no-store 請求和響應都不緩存
no-cache 至關於max-age:0,即資源被緩存,
可是緩存馬上過時, 同時下次訪問時強制驗證資源有效性
max-age 緩存資源, 可是在指定時間(單位爲秒)後緩存過時

Expires

http1.0中存在的字段,該字段所定義的緩存時間是相對服務器上的時間而言的,若是客戶端上的時間跟服務器上的時間不一致(特別是用戶修改了本身電腦的系統時間),那緩存時間可能就沒啥意義了。在HTTP 1.1版開始,使用Cache-Control: max-age=秒替代,這樣就不存在不一致問題了。

Last-Modified

Last-ModifiedIf-Modified-Since是一對的。

當瀏覽器第一次請求一個url時,服務器端的返回狀態碼爲200,同時HTTP響應頭會有一個Last-Modified標記着文件在服務器端最後被修改的時間。

瀏覽器第二次請求上次請求過的url時,瀏覽器會在HTTP請求頭添加一個If-Modified-Since的標記,用來詢問服務器該時間以後文件是否被修改過。

可是Last-Modified是http1.0的產物,有兩個缺點:

  • 只能精確到秒級別
  • 內容徹底沒改變的資源文件,沒法識別出來(只要修改時間變了,就算變更)。

全部就有了ETag

ETag

ETag解決了Last-Modified的缺點,http1.1的字段,優先級高於Last-Modified

理論上是ETag優先於Last-Modified,可是Nginx一會把這兩個一塊兒開啓一塊兒驗證,並且Nginx ETag的計算方式把最後修改時間也算進去了(全部這個算是弱ETag)。

Nginx ETag計算方式:計算頁面文件的最後修改時間,將文件最後修改時間的秒級Unix時間戳轉爲16進製做爲etag的第一部分 計算頁面文件的大小,將大小字節數轉爲16進製做爲etag的第二部分。

ETag有兩種類型:

  • 強ETag

    強ETag值,不論實體發生多麼細微的變化都會改變其值。

    強ETag表示形式:"22FAA065-2664-4197-9C5E-C92EA03D0A16"

  • 弱ETag

    弱 ETag 值只用於提示資源是否相同。只有資源發生了根本改變產 生差別時纔會改變 ETag 。這時,會在字段值最開始處附加 W/

    弱ETag表現形式:W/"22FAA065-2664-4197-9C5E-C92EA03D0A16"

ETag和If-None-Match是一對:

當瀏覽器第一次請求一個url時,服務器端的返回狀態碼爲200,同時HTTP響應頭會有一個Etag,存放着服務器端生成的一個序列值。

瀏覽器第二次請求上次請求過的url時,瀏覽器會在HTTP請求頭添加一個If-None-Match的標記,用來詢問服務器該文件有沒有被修改。

通常網站都會把Last-ModifiedETag一塊兒用,同時對對比,兩個條件都知足了纔會返回304。

Nginx實例

用實例來講明,Nginx的安裝和使用請自行網上學習,例子的環境是在Mac系統運行的,而後在谷歌瀏覽器上訪問(不一樣瀏覽器的表現會有點不同的,並且瀏覽器還有快捷鍵直接刷新跳過緩存的)。

注意:nginx在沒有設置Cache-Control:max-age=xxxexpires時,谷歌訪問後,後面會變成200(from memory cache),而後就形成了文件修改後沒法更新的問題(不知道何時過時)。這個也很好解決,只要設置過時時間爲0,這樣就必定不是強緩存,就不存在這些問題。

下面的例子Http Header只須要關注上面提到的相關字段。

ETag和Last-Modified例子

Nginx默認開啓ETagLast-Modified

因爲Nginx ETag可知,ETagLast-Modified多了文件大小比較,理論上有ETag就能夠不用Last-Modified,可是爲了兼容http1.0,不少web服務器都會帶上Last-Modified

Etag關閉以下:

etag off;

Last-Modified關閉以下(沒有找到具體關閉方式,只好在響應頭中直接賦值爲空):

add_header 'Last-Modified' '' always;

這些配置,能夠隨便設置在不一樣層級,http、server、location均可以。

默認的Nginx是同時開啓的,因此不用處理什麼。

server {
  listen       80;
  server_name  localhost;
  location / {
    #定義本身的web服務根目錄
    root   /Users/Sam/www;
    #默認訪問文件夾時,訪問index.html或者index.htm文件
    index  index.html index.htm;
    location ~* \.(jpg|jpeg|gif|bmp|png|js|css){
      #nginx在沒有設置Cache-Control:max-age=xxx和expires時,
      #谷歌訪問後,後面會變成200(from memory cache),
      #而後就形成了文件修改後沒法更新的問題。
      #這個很好解決,只要設置過時時間爲0,這樣就必定不是強緩存,就不存在這些問題
        expires 0s;
    }
  }
}

而後在根目錄index.html中引入./test.js文件,而後訪問index.html

首次訪問,返回200,而後再次訪問纔會返回304。而後不管如何修改,只要文件被保存(即便內容不變),再次訪問瀏覽器返回200,而後再次訪問返回304(內容沒修改)。

首次訪問 Http Header(先清理緩存,纔算首次訪問)

#通用的header
Request URL: http://localhost/test.js
Request Method: GET
Status Code: 200 OK
Remote Address: 127.0.0.1:80
Referrer Policy: no-referrer-when-downgrade

#響應頭
Cache-Control: max-age=0
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/javascript
Date: Thu, 27 Sep 2018 03:21:00 GMT
ETag: W/"5bab7b36-15"
Expires: Thu, 27 Sep 2018 03:21:00 GMT
Last-Modified: Wed, 26 Sep 2018 12:27:34 GMT
Server: nginx/1.10.1
Transfer-Encoding: chunked

#請求頭
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive
Host: localhost
Referer: http://localhost/
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36

第二次訪問 Http Header(無修改)

#通用的header
Request URL: http://localhost/test.js
Request Method: GET
Status Code: 304 Not Modified
Remote Address: 127.0.0.1:80
Referrer Policy: no-referrer-when-downgrade

#響應頭
Cache-Control: max-age=0
Connection: keep-alive
Date: Thu, 27 Sep 2018 03:23:22 GMT
ETag: "5bab7b36-15"
Expires: Thu, 27 Sep 2018 03:23:22 GMT
Last-Modified: Wed, 26 Sep 2018 12:27:34 GMT
Server: nginx/1.10.1

#請求頭
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive
Host: localhost
If-Modified-Since: Wed, 26 Sep 2018 12:27:34 GMT
If-None-Match: W/"5bab7b36-15"
Referer: http://localhost/
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36

第三次訪問 Http Header(有修改)

#通用的header
Request URL: http://localhost/test.js
Request Method: GET
Status Code: 200 OK
Remote Address: 127.0.0.1:80
Referrer Policy: no-referrer-when-downgrade

#響應頭
Cache-Control: max-age=0
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/javascript
Date: Thu, 27 Sep 2018 03:25:14 GMT
ETag: W/"5bac4d98-15"
Expires: Thu, 27 Sep 2018 03:25:14 GMT
Last-Modified: Thu, 27 Sep 2018 03:25:12 GMT
Server: nginx/1.10.1
Transfer-Encoding: chunked

#請求頭
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive
Host: localhost
If-Modified-Since: Wed, 26 Sep 2018 12:27:34 GMT
If-None-Match: W/"5bab7b36-15"
Referer: http://localhost/
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36

第四次訪問 Http Header(無修改)

#通用的header
Request URL: http://localhost/test.js
Request Method: GET
Status Code: 304 Not Modified
Remote Address: 127.0.0.1:80
Referrer Policy: no-referrer-when-downgrade

#響應頭
Cache-Control: max-age=0
Connection: keep-alive
Date: Thu, 27 Sep 2018 03:26:46 GMT
ETag: "5bac4d98-15"
Expires: Thu, 27 Sep 2018 03:26:46 GMT
Last-Modified: Thu, 27 Sep 2018 03:25:12 GMT
Server: nginx/1.10.1

#請求頭
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive
Host: localhost
If-Modified-Since: Thu, 27 Sep 2018 03:25:12 GMT
If-None-Match: W/"5bac4d98-15"
Referer: http://localhost/
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36

Cache-Control 和 Expires

server {
  listen       80;
  server_name  localhost;
  location / {
    #定義本身的web服務根目錄
    root   /Users/Sam/www;
    #默認訪問文件夾時,訪問index.html或者index.htm文件
    index  index.html index.htm;
    location ~* \.(jpg|jpeg|gif|bmp|png|js|css){
      #設置30秒緩存有效期
        expires 30s;
    }
  }
}

而後在根目錄index.html中引入./test.js文件,而後訪問index.html

首次訪問,瀏覽器會返回200,而後再次訪問纔會返回200(from memory cache),而後30秒後過時訪問,若是文件沒修過過,會返回304,不然返回200,繼續訪問若是沒過時期,返回200(from memory cache)。

具體請看上面的瀏覽器緩存流程圖。

首次訪問 Http Header(先清理緩存,纔算首次)

#通用的header
Request URL: http://localhost/test.js
Request Method: GET
Status Code: 200 OK
Remote Address: 127.0.0.1:80
Referrer Policy: no-referrer-when-downgrade

#響應頭
Cache-Control: max-age=30
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/javascript
Date: Thu, 27 Sep 2018 03:28:03 GMT
ETag: W/"5bac4d98-15"
Expires: Thu, 27 Sep 2018 03:28:33 GMT
Last-Modified: Thu, 27 Sep 2018 03:25:12 GMT
Server: nginx/1.10.1
Transfer-Encoding: chunked

#請求頭
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive
Host: localhost
Referer: http://localhost/
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36

第二次訪問 Http Header(不能過時,上面設置的是30秒,要在上次訪問的30秒內再次訪問)

#通用的header
Request URL: http://localhost/test.js
Request Method: GET
Status Code: 200 OK (from memory cache)
Remote Address: 127.0.0.1:80
Referrer Policy: no-referrer-when-downgrade

#響應頭
Cache-Control: max-age=30
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/javascript
Date: Thu, 27 Sep 2018 03:28:03 GMT
ETag: W/"5bac4d98-15"
Expires: Thu, 27 Sep 2018 03:28:33 GMT
Last-Modified: Thu, 27 Sep 2018 03:25:12 GMT
Server: nginx/1.10.1
Transfer-Encoding: chunked

#請求頭
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive
Host: localhost
Referer: http://localhost/
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36

第三次訪問 Http Header(須要過時,上面設置的是30秒,上一次訪問等待30秒後訪問)

#通用的header
Request URL: http://localhost/test.js
Request Method: GET
Status Code: 304 Not Modified
Remote Address: 127.0.0.1:80
Referrer Policy: no-referrer-when-downgrade

#響應頭
Cache-Control: max-age=30
Connection: keep-alive
Date: Thu, 27 Sep 2018 03:32:44 GMT
ETag: "5bac4d98-15"
Expires: Thu, 27 Sep 2018 03:33:14 GMT
Last-Modified: Thu, 27 Sep 2018 03:25:12 GMT
Server: nginx/1.10.1

#請求頭
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive
Host: localhost
If-Modified-Since: Thu, 27 Sep 2018 03:25:12 GMT
If-None-Match: W/"5bac4d98-15"
Referer: http://localhost/
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36

第四次訪問 Http Header(不能過時,上面設置的是30秒,要在上次訪問的30秒內再次訪問修改後的文件)

訪問獲取的仍是舊文件,文件雖然修改了,可是瀏覽器直接緩存中獲取,沒發出請求,沒法獲取最新的內容。

#通用的header
Request URL: http://localhost/test.js
Request Method: GET
Status Code: 200 OK (from memory cache)
Remote Address: 127.0.0.1:80
Referrer Policy: no-referrer-when-downgrade

#響應頭
Cache-Control: max-age=30
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/javascript
Date: Thu, 27 Sep 2018 03:28:03 GMT
ETag: W/"5bac4d98-15"
Expires: Thu, 27 Sep 2018 03:28:33 GMT
Last-Modified: Thu, 27 Sep 2018 03:25:12 GMT
Server: nginx/1.10.1

#請求頭
Transfer-Encoding: chunked
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive
Host: localhost
Referer: http://localhost/
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36

第五次訪問 Http Header(需過時,在第四次訪問後等待30秒訪問修改後的文件)

訪問後獲取到了最新文件。

#通用的header
Request URL: http://localhost/test.js
Request Method: GET
Status Code: 200 OK
Remote Address: 127.0.0.1:80
Referrer Policy: no-referrer-when-downgrade

#響應頭
Cache-Control: max-age=30
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/javascript
Date: Thu, 27 Sep 2018 03:36:51 GMT
ETag: W/"5bac4fcc-15"
Expires: Thu, 27 Sep 2018 03:37:21 GMT
Last-Modified: Thu, 27 Sep 2018 03:34:36 GMT
Server: nginx/1.10.1
Transfer-Encoding: chunked

#請求頭
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive
Host: localhost
If-Modified-Since: Thu, 27 Sep 2018 03:25:12 GMT
If-None-Match: W/"5bac4d98-15"
Referer: http://localhost/
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36

不須要緩存的場景

緩存是會提高訪問速度、節省帶寬、減輕服務器壓力,可是也不能濫用,不然會出現一些意想不到的問題。圖片、css、js等資源文件通常都是須要緩存的,可是像接口數據等數據會變更的http請求都是不須要緩存的,不然會形成沒法訪問到新數據的狀況。

場景

  • html文件不須要強緩存協商緩存就好了。

    這些都是特殊狀況,html文件能夠返回304狀態,可是不要返回200(from memory cache)。html文件最好設置過時時間爲0,強制跟服務器作文件修改對比(固然具體場景具體分析)。

    由於js文件和css文件是可使用版本作控制或者隨機數。

  • js代碼版本迭代更新

    這個場景不是不須要緩存,而是更新了js代碼版本,可是若是用戶還在緩存期內,就會致使頁面出錯。

    這種狀況就須要進行js類庫版本控制,如:

    <script src="../js/jquery.js?version=1.8.9"></script>

    升級到到2.0.0時,咱們須要把代碼改爲

    <script src="../js/jquery.js?version=2.0.0"></script>

    這樣就不會訪問到緩存的jquery.js。緩存是根據url來處理的,只要url不同就是新的資源。

  • 先後端使用ajax請求接口數據

解決方式

  • Url添加隨機數

    這種狀況,是前端作處理。

  • 請求頭添加Cache-Control: no-cache

    爲了兼容http1.0,而外添加Pragma: no-cache,Cache-Control的選項有不少,具體如何選擇,看場景。

    前端或者服務端均可以處理。

一些說明

作一些補充說明。

memory cache 和 disk cache

這個兩個說明是谷歌瀏覽器的狀態碼附加提示語,內存緩存和磁盤緩存。其實很簡單,只要頁面在網頁上打開訪問,而後不關閉,刷新的必定是from memory cache,而頁面關閉在打開必定是from disk cache。這是瀏覽器自身的緩存手段,磁盤緩存必定會有一份備份的,而後頁面訪問的時候也會在內存中緩存一份,這樣刷新當前頁面就不會讀取硬盤,而是直接內存中獲取(減小訪問磁盤的次數)。

不一樣瀏覽器的一些表現差別

不一樣瀏覽器的緩存手段是一致的,可是文案展示形式,和刷新頁面的方式會有差別(如火狐瀏覽器點擊刷新按鈕和回車刷新是不同的)。

下面是針對已經強緩存,同時訪問文件無修改的狀況下,不一樣刷新方式返回的狀態碼總結(mac系統):

瀏覽器 點擊地址欄刷新按鈕 回車刷新 cmd + r刷新
谷歌 200(from memory cache) 200(from memory cache) 200(from memory cache)
火狐 304 200,其餘地方說明已緩存 304
safari 200,其餘地方說明已緩存 200,其餘地方說明已緩存 200,其餘地方說明已緩存

這裏只說明下可能會遇到的疑惑。瀏覽器軟件自身的處理方式,跟http緩存掛不上鉤,咱們也沒法處理。

參考文章

相關文章
相關標籤/搜索