<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
瀏覽器快取是效能優化非常重要的一個方案,合理地使用快取可以提高使用者體驗,還能節省伺服器的開銷。掌握好快取的原理和併合理地使用無論對前端還是運維都是相當重要的。
瀏覽器快取(http 快取) 是指瀏覽器在本地磁碟對使用者最近請求過的檔案進行儲存,當存取者再次存取同一頁面時,瀏覽器就可以直接從本地磁碟載入檔案。
減少了冗餘的資料傳輸,節省頻寬,減少伺服器壓力
加快了使用者端載入速度,提升使用者體驗。
強快取不會向伺服器傳送請求,而是直接從快取中讀取資源,強快取可以通過設定兩種 HTTP Header 實現:Expires 和 Cache-Control,這兩個頭部分別是HTTP1.0和HTTP1.1的實現。
Expires是HTTP1.0提出的一個表示資源過期時間的header,它描述的是一個絕對時間,由伺服器返回。
Expires 受限於本地時間,如果修改了本地時間,就會造成快取失效。
Cache-Control 出現於 HTTP/1.1,常見欄位是max-age,單位是秒,很多web伺服器都有預設設定,優先順序高於Expires,表示的是相對時間。
例如Cache-Control:max-age=3600 代表資源的有效期是 3600 秒。取的是響應頭中的 Date,請求傳送的時間,表示當前資源在 Date ~ Date +3600s 這段時間裡都是有效的。Cache-Control 還擁有多個值:
要注意的就是no-cache和no-store的區別,no-cache是跳過強快取,還是會走協商快取的步驟,而no-store是真正的完全不走快取,所有資源都不會快取在本地
當瀏覽器對某個資源的請求沒有命中強快取,就會發一個請求到伺服器,驗證協商快取是否命中,如果協商快取命中,請求響應返回的http狀態為304並且會顯示一個Not Modified的字串。
協商快取用的是【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】這兩對Header來管理的。
注意!!協商快取需要配合強快取使用,使用協商快取需要先設定Cache-Control:no-cache或者pragma:no-cache來告訴瀏覽器不走強快取
這兩個Header是HTTP1.0版本提出來的,兩個欄位配合使用。
Last-Modified 表示本地檔案最後修改日期,瀏覽器會在請求頭帶上If-Modified-Since(上次返回的Last-Modified的值),伺服器會將這個值與資源修改的時間匹配,如果時間不一致,伺服器會返回新的資源,並且將 Last-Modified 值更新,作為響應頭返回給瀏覽器。如果時間一致,表示資源沒有更新,伺服器返回 304 狀態碼,瀏覽器拿到響應狀態碼後從本地快取中讀取資源。
但Last-Modified有幾個問題。
所以出現了ETAG。
在HTTP1.1版本中,伺服器通過 Etag 來設定響應頭快取標識。Etag 的值由伺服器端生成。在第一次請求時,伺服器會將資源和 Etag 一併返回給瀏覽器,瀏覽器將兩者快取到本地快取資料庫。在第二次請求時,瀏覽器會將 Etag 資訊放到 If-None-Match 請求頭去存取伺服器,伺服器收到請求後,會將伺服器中的檔案標識與瀏覽器發來的標識進行對比,如果不相同,伺服器返回更新的資源和新的 Etag ,如果相同,伺服器返回 304 狀態碼,瀏覽器讀取快取。
流程總結
總結這幾個欄位:
本文用koa來做例子,因為koa是更輕量級的、更純淨的,本身並沒有捆綁任何中介軟體,相比express自帶了很多router、static等多種中介軟體函數,koa更適合本文來做範例。
秉著學習和更容易理解的宗旨,不使用koa-static和koa-router中介軟體,用koa簡易實現web伺服器來驗證之前的結論。
# 建立並進入一個目錄並新建index.js檔案 mkdir koa-cache cd koa-cache touch index.js # 初始化專案 git init yarn init # 將 koa 安裝為本地依賴 yarn add koa
/*app.js*/ const Koa = require('koa') const app = new Koa() app.use(async (ctx) => { ctx.body = 'hello koa' }) app.listen(3000, () => { console.log('starting at port 3000') })
node index.js
這樣一個koa服務就起來了,存取localhost:3000可以就看到hello koa。
為了方便偵錯,修改程式碼不用重新啟動,推薦使用nodemon或者pm2啟動服務。
實現一個靜態資源伺服器關鍵點就是根據前端請求的地址來判斷請求的資源型別,設定返回的Content-Type,讓瀏覽器知道返回的內容型別,瀏覽器才能決定以什麼形式,什麼編碼來讀取返回的內容。
const mimes = { css: 'text/css', less: 'text/css', gif: 'image/gif', html: 'text/html', ico: 'image/x-icon', jpeg: 'image/jpeg', jpg: 'image/jpeg', js: 'text/javascript', json: 'application/json', pdf: 'application/pdf', png: 'image/png', svg: 'image/svg+xml', swf: 'application/x-shockwave-flash', tiff: 'image/tiff', txt: 'text/plain', wav: 'audio/x-wav', wma: 'audio/x-ms-wma', wmv: 'video/x-ms-wmv', xml: 'text/xml', }
function parseMime(url) { // path.extname獲取路徑中檔案的字尾名 let extName = path.extname(url) extName = extName ? extName.slice(1) : 'unknown' return mimes[extName] }
const parseStatic = (dir) => { return new Promise((resolve) => { resolve(fs.readFileSync(dir), 'binary') }) }
app.use(async (ctx) => { const url = ctx.request.url if (url === '/') { // 存取根路徑返回index.html ctx.set('Content-Type', 'text/html') ctx.body = await parseStatic('./index.html') } else { ctx.set('Content-Type', parseMime(url)) ctx.body = await parseStatic(path.relative('/', url)) } })
這樣基本也就完成了一個簡單的靜態資源伺服器。然後在根目錄下新建一個html檔案和static目錄,並在static下放一些檔案。這時候的目錄應該是這樣的:
|-- koa-cache |-- index.html |-- index.js |-- static |-- css |-- color.css |-- ... |-- image |-- soldier.png |-- ... ... ...
這時候就可以通過localhost:3000/static存取具體的資原始檔了。
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>test cache</title> <link rel="stylesheet" href="/static/css/index.css" rel="external nofollow" /> </head> <body> <div id="app">測試css檔案</div> <img src="/static/image/soldier.png" alt="" /> </body> </html>
css/color.css
#app { color: blue; }
這時候開啟localhost:3000,就能看到如下效果:
到這裡基本的環境就都搭好了。接下來進入驗證階段。
在沒有任何設定之前,可以看下network:
這時候無論是首次還是第幾次,都會向伺服器請求資源。
注意!!!在開始實驗之前要把network面板的Disable cache勾選去掉,這個選項表示禁用瀏覽器快取,瀏覽器請求會帶上Cache-Control: no-cache和Pragma: no-cache頭部資訊,這時候所有的請求都不會走快取
修改index.js中的app.use程式碼段。
app.use(async (ctx) => { const url = ctx.request.url if (url === '/') { // 存取根路徑返回index.html ctx.set('Content-Type', 'text/html') ctx.body = await parseStatic('./index.html') } else { const filePath = path.resolve(__dirname, `.${url}`) ctx.set('Content-Type', parseMime(url)) // 設定過期時間在30000毫秒,也就是30秒後 ctx.set('Expires', new Date(Date.now() + 30000)) ctx.body = await parseStatic(filePath) } })
用ctx.set(‘Expires’, new Date(Date.now() + 30000)),設定過期時間為當期時間的30000毫秒,也就是30秒後(後面的設定頭部資訊都是這裡修改)。
再存取下localhost:3000,可以看到多了Expires這個Header。
後面在30秒之記憶體取都可以看到network的Size,css檔案顯示的是disk cache,而image資源顯示的是from memory cache。這時候瀏覽器是直接讀的瀏覽器快取,並沒有請求伺服器,可以嘗試把css和圖片檔案改名稱或者刪除驗證下,頁面顯示正常,說明之前的結論是沒錯的。
ctx.set(‘Cache-Control’, ‘max-age=300’)設定300秒有效期,驗證方式同上。
HTTP1.0協商快取關鍵點就是根據使用者端請求帶的ifModifiedSince欄位的時間和請求的資源對應的修改時間來判斷資源是否有更新。
首先設定Cache-Control: no-cache, 使使用者端不走強快取,再判斷使用者端請求是否有帶ifModifiedSince欄位,沒有就設定Last-Modified欄位,並返回資原始檔。如果有就用fs.stat讀取資原始檔的修改時間,並進行對比,如果時間一樣,則返回狀態碼304。
ctx.set('Cache-Control', 'no-cache') const ifModifiedSince = ctx.request.header['if-modified-since'] const fileStat = await getFileStat(filePath) if (ifModifiedSince === fileStat.mtime.toGMTString()) { ctx.status = 304 } else { ctx.set('Last-Modified', fileStat.mtime.toGMTString()) ctx.body = await parseStatic(filePath) }
etag的關鍵點在於計算資原始檔的唯一性,這裡使用nodejs內建的crypto模組來計算檔案的hash值,並用十六進位制的字串表示。cypto的用法可以看nodejs的官網。
crpto不僅支援字串的加密,還支援傳入buffer加密,作為nodejs的內建模組,在這裡用來計算檔案的唯一標識再合適不過。
ctx.set('Cache-Control', 'no-cache') const fileBuffer = await parseStatic(filePath) const ifNoneMatch = ctx.request.headers['if-none-match'] const hash = crypto.createHash('md5') hash.update(fileBuffer) const etag = `"${hash.digest('hex')}"` if (ifNoneMatch === etag) { ctx.status = 304 } else { ctx.set('etag', etag) ctx.body = fileBuffer }
效果如下圖,第二次請求瀏覽器會帶上If-None-Match,伺服器計算檔案的hash值再次比較,相同則返回304,不同再返回新的檔案。而如果修改了檔案,檔案的hash值也就變了,這時候兩個hash不匹配,伺服器則返回新的檔案並帶上新檔案的hash值作為etag。
通過以上程式碼實踐了每個快取欄位的效果,程式碼僅作為演示,生產的靜態資源伺服器會更加複雜,例如etag不會每次都重新獲取檔案來計算檔案的hash值,這樣太費效能,一般都會有響應的快取機制,比如對資源的 last-modified 和 etag 值建立索引快取。
通常web伺服器都有預設的快取設定,具體的實現可能也不大相同,像nginx、tomcat、express等web伺服器都有相應的原始碼,有興趣的可以去閱讀學習。
合理的使用強快取和協商快取具體需要看專案的使用場景和需求。像目前常見的單頁面應用,因為通常打包都是新生成html與相應的靜態資源依賴,所以可以對html檔案設定協商快取,而打包生成的依賴,例如js、css這些檔案可以使用強快取。或者只對第三方庫使用強快取,因為第三方庫通常版本更新較慢,可以鎖定版本。
node範例完整程式碼 https://github.com/chen-junyi/code/blob/main/node/cache/koa2.js
以上就是node強快取和協商快取實戰範例的詳細內容,更多關於node強快取協商快取的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45