HTTP前端優(yōu)化之壓縮與緩存

現(xiàn)代的瀏覽器以及服務器都支持壓縮技術,唯一需要協(xié)商的是采用的壓縮算法。 為了選擇采用的壓縮算法,瀏覽器和服務器之間會使用?主動協(xié)商機制

瀏覽器發(fā)送?Accept-Encoding?首部,(其中包含它所支持的壓縮算法,以及各自優(yōu)先級)

服務器則從中選擇一種,使用該算法對響應消息主體進行壓縮,并且發(fā)送?Content-Encoding首部來告知瀏覽器它使用了哪一種算法

由于該內(nèi)容協(xié)商過程是基于編碼類型來選擇資源的展現(xiàn)形式的,在響應中,?Vary?(渲染引擎)首部中至少要包含?Accept-Encoding?;這樣,緩存服務器就可以對資源的不同的展現(xiàn)形式進行緩存。

如果有想學習編程的初學者,可來我們的前端直播授課群的哦:733395506里面免費送整套系統(tǒng)的前端教程!

如下圖:

也就是:

客戶端(HTTP請求頭) -------> accept-encoding: gzip,deflate,br,sdch

服務器(HTTP響應頭) -------> content-encoding:gzip

實例

gzip(filePath, req,res, statObj) {letencoding = req.headers["accept-encoding"]if(encoding) {if(encoding.match(/gzip/)) {res.setHeader("Content-Encoding","gzip")returnzlib.createGzip()? ? ? ? ? ? }elseif(encoding.match(/deflate/)) {res.setHeader("Content-Encoding","deflate")returnzlib.createDeflate()? ? ? ? ? ? }returnfalse? ? ? ? }returnfalse? ? }letflag = this.gzip(filePath, req,res, statObj)lettype= mime.getType(filePath) ||"text/plain"res.setHeader("Content-Type",type+"; charset=utf8")if(!flag){? ? ? ? fs.createReadStream(filePath).pipe(res)? ? }else{? ? ? ? fs.createReadStream(filePath).pipe(flag).pipe(res)? ? }復制代碼

壓縮的優(yōu)缺點

優(yōu)點:減少HTTP響應時間,提升傳輸效率

壓縮過程占用服務器額外的CPU周期,客戶端也要對壓縮文件進行解壓縮,這也需要占用部分時間。

總結:

請求頭:

Accept-Encoding: gzip,deflate,br,sdch;告知服務器自己支持的壓縮格式

user-agent: 不同設備自動帶上這個頭,可以判斷什么樣的設備,重定向到相同的項目,實現(xiàn)不同設備響應不同項目

響應頭:

COntent-encoding:gzip; 告知瀏覽器,服務器使用的壓縮格式

Content-Type: 服務器給瀏覽器響應內(nèi)容的類型

Location: 重定向到某個地方

2.緩存

假設瀏覽器存在一個緩存數(shù)據(jù)庫,用于存儲緩存信息。

在客戶端第一次請求數(shù)據(jù)時,此時緩存數(shù)據(jù)庫中沒用對應的緩存數(shù)據(jù),需要請求服務器,服務器返回后,將數(shù)據(jù)存儲至緩存數(shù)據(jù)庫中。

HTTP緩存有多種規(guī)則,根據(jù)是否需要重新向服務器發(fā)起請求進行分類 將其分為兩大類(**強制緩存,對比緩存又叫協(xié)商緩存)

1. 已存在緩存數(shù)據(jù),僅基于強制緩存,請求數(shù)據(jù)如下

2. 已存在緩存數(shù)據(jù),僅基于對比緩存,請求數(shù)據(jù)如下

兩類緩存規(guī)則不同:

強制緩存如果生效,不需要再和服務器發(fā)生交互

對比緩存(協(xié)商緩存)不管是否生效,都需要與服務器端發(fā)生交互

**兩類緩存規(guī)則同時存在時,強制緩存優(yōu)先級高于對比緩存,也就是說,當執(zhí)行強制緩存的規(guī)則時,如果緩存生效,直接使用緩存,不再執(zhí)行對比緩存規(guī)則

強制緩存

在沒有緩存數(shù)據(jù)時,瀏覽器向服務器請求數(shù)據(jù)時,服務器會將數(shù)據(jù)和緩存規(guī)則一并返回,?緩存規(guī)則信息包含在響應header中?;

對于強制緩存來說,響應頭中會有連個字段表名失效規(guī)則(?Expires/Cache-Control?)

Expires

Expries的值為服務器端返回的到期時間,即下一次請求時,請求的時間小于服務端返回到期時間,直接使用緩存數(shù)據(jù)。不過Expries是HTTP1.0的東西,現(xiàn)在瀏覽器默認使用的是HTTP1.1,所以它的作用基本忽略

另一個問題,到期時間使用服務端生成的,但是客戶端時間可能跟服務端時間有誤差,這就導致了緩存命中的誤差,因此,HTTP1.1版本中,使用了Cache-Control替代

Cache-Control

Cache-Control是最重要的規(guī)則,其常見取值:

private:客戶端可以緩存

public:客戶端和代理服務器都可緩存

max-age=xxx:緩存的內(nèi)容將在xxx秒后失效

no-cache:需要使用對比緩存來驗證緩存數(shù)據(jù)

no-store:所有內(nèi)容都不會緩存,?強制緩存,對比緩存都不觸發(fā)?,

實例:

圖中Cache-Control僅指定了max-age,所有默認是private,緩存時間是31536000秒(365天) 也就是說,在365天內(nèi)再次請求這條數(shù)據(jù),都會直接獲取緩存數(shù)據(jù)庫中的數(shù)據(jù),直接使用。

對比緩存(協(xié)商緩存)

瀏覽器第一次請求數(shù)據(jù)時,服務器會將緩存標識u數(shù)據(jù)一起返回給客戶端,客戶端將二者備份至緩存數(shù)據(jù)庫中, 再次請求數(shù)據(jù)時,客戶端將備份的數(shù)據(jù)標識發(fā)送給服務器,服務器根據(jù)緩存標識進行判斷,判斷成功后,返回304狀態(tài)碼,通知客戶端比較成功,可以使用緩存數(shù)據(jù)

通過兩圖對比,可發(fā)現(xiàn),在對比緩存生效時,狀態(tài)碼是304,并且報文大小和請求時間大大減少。 原因是,服務器在進行標識比較后,只返回header部分,通過狀態(tài)碼通知客戶端使用緩存,不再需要將報文主體部分返回給客戶端。

緩存標識:

Last-Modified/If-Modified-Since

Last-Modified:

服務器在響應請求時,告訴瀏覽器資源的最后修改時間。

If-Modified-Since:

再次請求服務器時,通過此字段通知服務器上次請求時,服務器返回的資源最后的修改時間。 服務器收到請求后發(fā)現(xiàn)頭有 If-Modified-Since 則與請求資源的最后修改時間進行對比。 若資源的最后修改時間大于 If-Modified-since ,說明資源又被改動過,則響應整片資源內(nèi)容,返回狀態(tài)碼200; 若資源的最后修改時間小于或等于 If-Modified-Since ,說明資源沒有新修改過,則響應HTTP304,告訴繼續(xù)使用緩存中的數(shù)據(jù)

**Etag/If-None-Match

優(yōu)先級高于 Last-Modified/If-Modified-Since

Etag:

服務器響應請求時,告訴瀏覽器當前資源在服務器的唯一標識(摘要)

If-None-Match:

再次請求服務器時,通過此字段通知服務器客戶端緩存數(shù)據(jù)的唯一標識, 服務器收到請求后發(fā)現(xiàn)請求頭中有 If-None-Match 則與被請求資源的唯一標識進行比對, 不同,說明資源又被改動過,則響應整片資源內(nèi)容,返回狀態(tài)碼200; 相同,說明資源沒有新修改過,則響應HTTP304,告知瀏覽器使用緩存數(shù)據(jù)

總結

對于強制緩存,服務器通知瀏覽器一個緩存時間,在緩存時間內(nèi),下次請求,直接用緩存,不在時間內(nèi),執(zhí)行比較緩存策略:

Expries/Cache-Control:max-age=xxx > Etag/If-None-Match > Last-Modified/If-Modified-Since

對于比較緩存,將緩存信息中的Etage和Last-Modified通過請求發(fā)送給服務器,由服務器校驗,返回304狀態(tài)碼時,瀏覽器直接使用緩存數(shù)據(jù)。

瀏覽器第一次請求:

瀏覽器再次請求時:

實例:

cache(filePath, req, res,statObj){letlastModified =statObj.ctime.toGMTString()letifModifiedSince = req.headers['if-modified-since']letEtag =? ? ? ? ? ? crypto.createHash("md5").update(fs.readFileSync(filePath)).digest("base64")? ? ? ? res.setHeader("Last-Modified",lastModified)? ? ? ? // Etag是響應頭? ? ? ? res.setHeader("Etag",Etag)? ? ? ? //if-none-match 當你修改服務器上的文件時,請求頭上面會自動添加這個頭? ? ? ? // console.log(req.headers['if-none-match'],"match")? ? ? ? // console.log(Etag)? ? ? ? //if-none-match: NISthsES8P9vzWjdFT/xyg== match? ? ? ? // console.log(req.headers['if-none-match'])? ? ? ? // T9hRJPsOY4/I9QhWp+NFlQ==? ? ? ? // 如果if-none-match存在,說明你改動服務器上的文件中的內(nèi)容? ? ? ? // T9hRJPsOY4/I9QhWp+NFlQ==? ? ? ? // console.log("Etag--->",Etag)? ? ? ? // console.log("if-none-match--->",req.headers['if-none-match'])letifNoneMatch = req.headers['if-none-match']? ? ? ? // // 根據(jù)內(nèi)容摘要判斷是否需要緩存? ? ? ? //if(ifNoneMatch){? ? ? ? //? ? //ifNoneMatch說明修改了內(nèi)容? ? ? ? //? ? //returnfalse;? ? ? ? //if(ifNoneMatch !== Etag){? ? ? ? //? ? ? ? // 修改了內(nèi)容,并且沒恢復,走網(wǎng)絡? ? ? ? //returnfalse;? // 不走緩存? ? ? ? //? ? }else{? ? ? ? //? ? ? ? // 修改了內(nèi)容,并且修改完后,把內(nèi)容恢復,相當于沒有修改? ? ? ? //returntrue; // 還是從緩存中取數(shù)據(jù)? ? ? ? //? ? }? ? ? ? // }else{? ? ? ? //? ? // 說明內(nèi)容沒有改變? ? ? ? //returntrue//? ? ? ? // }? ? ? ? // // 根據(jù)修改時間來判斷是否緩存? ? ? ? //if(ifModifiedSince){? ? ? ? //if(ifModifiedSince !== lastModified){? ? ? ? //? ? ? ? // 上一次修改的時間和最新修改的時間不一樣? ? ? ? //returnfalse//? 不走緩存? ? ? ? //? ? }? ? ? ? // }? ? ? ? // 壓縮和緩存是后端程序干的if(ifModifiedSince &&ifNoneMatch){if(ifNoneMatch !== Etag &&ifModifiedSince !== lastModified){returnfalse}? ? ? ? }else{returnfalse}returntrue} sendFile(filePath, req, res,statObj) {? ? ? ? res.setHeader("Cache-Control","no-cache");letcache = this.cache(filePath, req, res,statObj)if(cache){? ? ? ? ? ? res.statusCode = 304;returnres.end()? ? ? ? }letflag = this.gzip(filePath, req, res,statObj)lettype= mime.getType(filePath) ||"text/plain"res.setHeader("Content-Type",type+"; charset=utf8")if(!flag){? ? ? ? ? ? fs.createReadStream(filePath).pipe(res)? ? ? ? }else{? ? ? ? ? ? fs.createReadStream(filePath).pipe(flag).pipe(res)? ? ? ? }? ? }復制代碼

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容