您好,登錄后才能下訂單哦!
本篇內容介紹了“HTTP緩存的作用和規則簡介”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
前言
HTTP 緩存機制作為 Web 應用性能優化的重要手段,對于從事 Web 開發的同學們來說,應該是知識體系的基礎環節,也是想要成為前端架構的必備技能。
緩存的作用
我們為什么使用緩存,是因為緩存可以給我們的 Web 項目帶來以下好處,以提高性能和用戶體驗。
加快了瀏覽器加載網頁的速度;
減少了冗余的數據傳輸,節省網絡流量和帶寬;
減少服務器的負擔,大大提高了網站的性能。
由于從本地緩存讀取靜態資源,加快瀏覽器的網頁加載速度是一定的,也確實的減少了數據傳輸,就提高網站性能來說,可能一兩個用戶的訪問對于減小服務器的負擔沒有明顯效果,但如果這個網站在高并發的情況下,使用緩存對于減小服務器壓力和整個網站的性能都會發生質的變化。
緩存規則簡介
為了方便理解,我們認為瀏覽器存在一個緩存數據庫,用于存儲緩存信息(實際上靜態資源是被緩存到了內存和磁盤中),在瀏覽器第一次請求數據時,此時緩存數據庫沒有對應的緩存數據,則需要請求服務器,服務器會將緩存規則和數據返回,瀏覽器將緩存規則和數據存儲進緩存數據庫。
當瀏覽器地址欄輸入地址后請求的 index.html
是不會被緩存的,但 index.html
內部請求的其他資源會遵循緩存策略,HTTP 緩存有多種規則,根據是否需要向服務器發送請求主要分為兩大類,強制緩存和協商緩存。
強制緩存
1、強制緩存流程
強制緩存是第一次訪問服務器獲取數據后,在有效時間內不會再請求服務器,而是直接使用緩存數據,強制緩存的流程如下。
2、強制緩存判斷到期時間
那么如何判斷緩存是否到期呢?其實還是根據第一次訪問時服務器的響應頭來實現的,在 HTTP 1.0
版本和 HTTP 1.1
版本有所不同。
在 HTTP 1.0
版本,服務器使用的響應頭字段為 Expires
,值為未來的絕對時間(時間戳),瀏覽器請求時的當前時間超過了 Expires
設置的時間,代表緩存失效,需要再次向服務器發送請求,否則都會直接從緩存數據庫中獲取數據。
在 HTTP 1.1
版本,服務器使用的響應頭字段為 Cache-Control
,有多個值,意義各不相同。
private:客戶端可以緩存;
public:客戶端和代理服務器都可以緩存(對于前端而言,可以認為與 private
效果相同);
max-age=xxx:緩存的內容將在 xxx
秒后過期(相對時間,秒為單位);
no-cache:需要使用協商緩存(后面介紹)來驗證數據是否過期;
no-store:所有內容都不會緩存,強制緩存和協商緩存都不會觸發。
Cache-Control
的值中最常用的為 max-age=xxx
,緩存本身就是為了數據傳輸的優化和性能而存在的,所以 no-store
幾乎不會使用。
注意:在 HTTP 1.0
版本中,Expires
字段的絕對時間是從服務器獲取的,由于請求需要時間,所以瀏覽器的請求時間與服務器接收到請求所獲取的時間是存在誤差的,這也導致了緩存命中的誤差,在 HTTP 1.1
版本中,因為 Cache-Control
的值 max-age=xxx
中的 xxx
是以秒為單位的相對時間,所以在瀏覽器接收到資源后開始倒計時,規避了 HTTP 1.0
中緩存命中存在誤差的缺點,為了兼容低版本 HTTP 協議,正常開發中兩種響應頭會同時使用,HTTP 1.1
版本的實現優先級高于 HTTP 1.0
。
3、通過 Network 查看強制緩存
我們通過 Chrome 瀏覽器的開發者工具,打開 NetWork 查看強制緩存的相關信息。
上面是百度網站 Logo 圖片的響應,我們可以清楚的看到,其中兼容了 HTTP 1.0
和 HTTP 1.1
版本,并使用強制緩存存儲了 10
年。
下面看一看通過緩存取出的數據在 Network 中與其他資源的區別。
其實緩存的儲存是內存和磁盤兩個位置,由當前瀏覽器本身的策略決定,比較隨機,從內存的緩存中取出的數據會顯示 (from memory cache)
,從磁盤的緩存中取出的數據會顯示 (from disk cache)
。
4、NodeJS 服務器實現強制緩存
// 強制緩存 const http = require("http"); const url = require("url"); const path = require("path"); const mime = require("mime"); const fs = require("fs"); const server = http.createServer((req, res) => { let { pathname } = url.parse(req.url, true); pathname = pathname !== "/" ? pathname : "/index.html"; // 獲取讀取文件的絕對路徑 let p = path.join(__dirname, pathname); // 查看路徑是否合法 fs.access(p, err => { // 路徑不合法則直接中斷連接 if (err) return res.end("Not Found"); // 設置強制緩存 res.setHeader("Expires", new Date(Date.now() + 30000).toGMTString()); res.setHeader("Cache-Control", "max-age=30"); // 設置文件類型并響應給瀏覽器 res.setHeader("Content-Type", `${mime.getType(p)};charset=utf8`); fs.createReadStream(p).pipe(res); }); }); server.listen(3000, () => { console.log("server start 3000"); });
上面 mime
模塊的 getType
方法可以成功返回傳入路徑下文件對應的文件類型,如 text/html
和 application/javascript
等,是第三方模塊,使用之前需要安裝。
npm install mime
協商緩存
1、協商緩存流程
協商緩存又叫對比緩存,設置協商緩存后,第一次訪問服務器獲取數據時,服務器會將數據和緩存標識一起返回給瀏覽器,客戶端會將數據和標識存入緩存數據庫中,下一次請求時,會先去緩存中取出緩存標識發送給服務器進行詢問,當服務器數據更改時會更新標識,所以服務器拿到瀏覽器發來的標識進行對比,相同代表數據未更改,響應瀏覽器通知數據未更改,瀏覽器會去緩存中獲取數據,如果標識不同,代表服務器更改過數據,所以會將新的數據和新的標識返回瀏覽器,瀏覽器會將新的數據和標識存入緩存中,協商緩存的流程如下。
協商緩存和強制緩存不同的是,協商緩存每次請求都需要跟服務器通信,而且命中緩存服務器返回狀態碼不再是 200
,而是 304
。
2、協商緩存判斷標識
強制緩存是通過過期時間來控制是否訪問服務器,而協商緩存每次都要與服務器交互對比緩存標識,同樣的,對于協商緩存的實現在 HTTP 1.0
版本和 HTTP 1.1
版本也有所不同。
在 HTTP 1.0
版本中,服務器通過 Last-Modified
響應頭來設置緩存標識,通常取請求數據的最后修改時間(絕對時間)作為值,而瀏覽器將接收到返回的數據和標識存入緩存,再次請求會自動發送 If-Modified-Since
請求頭,值為之前返回的最后修改時間(標識),服務器取出 If-Modified-Since
的值與數據的上次修改時間對比,如果上次修改時間大于了 If-Modified-Since
的值,說明被修改過,則通過 Last-Modified
響應頭返回新的最后修改時間和新的數據,否則未被修改,返回狀態碼 304
通知瀏覽器命中緩存。
在 HTTP 1.1
版本中,服務器通過 Etag
響應頭來設置緩存標識(唯一標識,像一個指紋一樣,生成規則由服務器來決定),瀏覽器接收到數據和唯一標識后存入緩存,下次請求時,通過 If-None-Match
請求頭將唯一標識帶給服務器,服務器取出唯一標識與之前的標識對比,不同,說明修改過,返回新標識和數據,相同,則返回狀態碼 304
通知瀏覽器命中緩存。
HTTP 協商緩存策略流程圖如下:
注意:使用協商緩存時 HTTP 1.0
版本還是不太靠譜,假設一個文件增加了一個字符后又刪除了,文件相當于沒更改,但是最后修改時間變了,會被當作修改處理,本應該命中緩存,服務器卻重新發送了數據,因此 HTTP 1.1
中使用的 Etag
唯一標識是根據文件內容或摘要生成的,保證了只要文件內容不變,則一定會命中緩存,為了兼容低版本 HTTP 協議,開發中兩種響應頭也會同時使用,同樣 HTTP 1.1
版本的實現優先級高于 HTTP 1.0
。
3、通過 Network 查看協商緩存
我們同樣通過 Chrome 瀏覽器的開發者工具,打開 NetWork 查看協商緩存的相關信息。
再次請求服務器的請求頭信息:
命中協商緩存的響應頭信息:
下面看一看通過協商緩存取出的數據在 Network 中與第一次加載的區別。
第一次請求:
緩存后請求:
通過兩圖的對比,我們可以發現,協商緩存生效時的狀態碼為 304
,并且報文大小和請求時間大大減少,原因是服務端在進行標識比對后只返回了 header
部分,通過狀態碼來通知瀏覽器使用緩存,不再需要將報文主體部分一起返回給瀏覽器。
4、NodeJS 服務器實現協商緩存
// 協商緩存 const http = require("http"); const url = require("url"); const path = require("path"); const mime = require("mime"); const fs = require("fs");0 const crytpo = require("crytpo"); const server = http.createServer((req, res) => { let { pathname } = url.parse(req.url, true); pathname = pathname !== "/" ? pathname : "/index.html"; // 獲取讀取文件的絕對路徑 let p = path.join(__dirname, pathname); // 查看路徑是否合法 fs.stat(p, (err, statObj) => { // 路徑不合法則直接中斷連接 if (err) return res.end("Not Found"); let md5 = crypto.createHash("md5"); // 創建加密的轉換流 let rs = fs.createReadStream(p); // 創建可讀流 // 讀取文件內容并加密 rs.on("data", data => md5.update(data)); rs.on("end", () => { let ctime = statObj.ctime.toGMTString(); // 獲取文件最后修改時間 let flag = md5.digest("hex"); // 獲取加密后的唯一標識 // 獲取協商緩存的請求頭 let ifModifiedSince = req.headers["if-modified-since"]; let ifNoneMatch = req.headers["if-none-match"]; if (ifModifiedSince === ctime || ifNoneMatch === flag) { res.statusCode = 304; res.end(); } else { // 設置協商緩存 res.setHeader("Last-Modified", ctime); res.setHeader("Etag", flag); // 設置文件類型并響應給瀏覽器 res.setHeader("Content-Type", `${mime.getType(p)};charset=utf8`); rs.pipe(res); } }); }); }); server.listen(3000, () => { console.log("server start 3000"); });
在上面的代碼中是通過可讀流讀取文件內容,并通過 crypto
模塊進行了 md5
加密后的結果作為了唯一標識,這樣就能保證只要文件內容不變,就會命中緩存,其中兼容了 HTTP 1.0
和 HTTP 1.1
兩個版本,只要滿足一個則直接返回 304
通知瀏覽器命中緩存。
注意:其實讀取文件內容加密這種做法并不可取,假如讀取的是大文件,在讀取文件內容和進行 md5
加密這個過程會非常消耗時間,所以在開發中要針對業務的實際情況選擇可以保證服務器性能的方式生成唯一標識,比如根據文件的摘要。
總結
為了使緩存策略更加健壯、靈活,HTTP 1.0
版本 和 HTTP 1.1
版本的緩存策略會同時使用,甚至強制緩存和協商緩存也會同時使用,對于強制緩存,服務器通知瀏覽器一個緩存時間,在緩存時間內,下次請求,直接使用緩存,超出有效時間,執行協商緩存策略,對于協商緩存,將緩存信息中的 Etag
和 Last-Modified
通過請求頭 If-None-Match
和 If-Modified-Since
發送給服務器,由服務器校驗同時設置新的強制緩存,校驗通過并返回 304
狀態碼時,瀏覽器直接使用緩存,如果協商緩存也未命中,則服務器重新設置協商緩存的標識。
“HTTP緩存的作用和規則簡介”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。