來(lái)源:http://imququ.com/post/http2-and-wpo-1.html
作者:Jerry Qu
2013 年 11 月份開始,我的博客開始支持了 SPDY 協(xié)議(詳見這里),也就是 HTTP/2 的前身。今年二月份,Google 宣布將在 16 年初放棄對(duì) SPDY 的支持,隨后 Google 自家支持 SPDY 協(xié)議的服務(wù)都切到了 HTTP/2。今年 5 月 14 日,HTTP/2 以?RFC 7540?正式發(fā)布。目前,瀏覽器方面,Chrome 40+ 和 Firefox 36+ 都正式支持了 HTTP/2;服務(wù)器方面,著名的 Nginx 表示會(huì)在今年底正式支持 HTTP/2。
不得不說(shuō)這幾年 WEB 技術(shù)一直在突飛猛進(jìn),爆炸式發(fā)展。昨天還覺得 HTTP/2 很遙遠(yuǎn),今天已經(jīng)遍地都是了。對(duì)于新鮮事物,有些人不愿意接受,覺得好端端為什么又要折騰;有些人會(huì)盲目崇拜,認(rèn)為它是能拯救一切的救世主。HTTP/2 究竟會(huì)給前端帶來(lái)什么,什么都不是?還是像某些人說(shuō)的「讓前端那些優(yōu)化小伎倆直接退休」?我打算通過(guò)寫一系列文章來(lái)嘗試回答這個(gè)問(wèn)題,今天是第一篇。
我們知道,一個(gè)頁(yè)面通常由一個(gè) HTML 文檔和多個(gè)資源組成。有一些很重要的資源,例如頭部的 CSS、關(guān)鍵的 JS,如果遲遲沒(méi)有加載完,會(huì)阻塞頁(yè)面渲染或?qū)е掠脩魺o(wú)法交互,體驗(yàn)很差。如何讓重要的資源更快加載完是我本文要討論的問(wèn)題。
我們先來(lái)考慮資源外鏈的情況。通常,外鏈資源都會(huì)部署在 CDN 上,這樣用戶就可以從離自己最近的節(jié)點(diǎn)上獲取數(shù)據(jù)。一般文本文件都會(huì)采用 gzip 壓縮,實(shí)際傳輸大小是文件大小的幾分之一。服務(wù)端托管靜態(tài)資源的效率通常非常高,服務(wù)端處理時(shí)間幾乎可以忽略。在忽略網(wǎng)絡(luò)因素、傳輸大小以及服務(wù)端處理時(shí)間之后,用戶何時(shí)能加載完外鏈資源,很大程度上取決于請(qǐng)求何時(shí)能發(fā)出去,這主要受下面三個(gè)因素影響:
當(dāng)然我們一般都會(huì)給靜態(tài)資源設(shè)置一個(gè)很長(zhǎng)時(shí)間的緩存頭。只要用戶不清除瀏覽器緩存也不刷新,第二次訪問(wèn)我們網(wǎng)頁(yè)時(shí),靜態(tài)資源會(huì)直接從本地緩存獲取,并不產(chǎn)生網(wǎng)絡(luò)請(qǐng)求;如果用戶只是普通刷新而不是強(qiáng)刷,瀏覽器會(huì)在請(qǐng)求頭帶上協(xié)商字段?If-Modified-Since
?或?If-None-Match
,服務(wù)端對(duì)沒(méi)有變化的資源會(huì)響應(yīng) 304 狀態(tài)碼,告知瀏覽器從本地緩存獲取資源。304 請(qǐng)求沒(méi)有正文,非常小。
也就是說(shuō)資源外鏈的特點(diǎn)是,第一次慢,第二次快。
再來(lái)看看資源內(nèi)聯(lián)的情況。把 CSS、JS 文件內(nèi)容直接內(nèi)聯(lián)在 HTML 中的方案,毫無(wú)疑問(wèn)會(huì)在用戶第一次訪問(wèn)時(shí)有速度優(yōu)勢(shì)。但通常我們很少緩存 HTML 頁(yè)面,這種方案會(huì)導(dǎo)致內(nèi)聯(lián)的資源沒(méi)辦法利用瀏覽器緩存,后續(xù)每次訪問(wèn)都是一種浪費(fèi)。
很早之前,就有網(wǎng)站開始針對(duì)第一次訪問(wèn)的用戶將資源內(nèi)聯(lián),并在頁(yè)面加載完之后異步加載這些資源的外鏈版本,同時(shí)記錄一個(gè) Cookie 標(biāo)記表示用戶來(lái)過(guò)。用戶再次訪問(wèn)這個(gè)頁(yè)面時(shí),服務(wù)端就可以輸出只有外鏈版本的頁(yè)面,減小體積。
這個(gè)方案除了有點(diǎn)浪費(fèi)流量之外(一份資源,內(nèi)聯(lián)外鏈加載了兩次),基本上能達(dá)到更快加載重要資源的效果。但是在流量更加寶貴的移動(dòng)端,我們需要繼續(xù)改進(jìn)這個(gè)方案。
考慮到移動(dòng)端瀏覽器都支持 localStorage,可以將第一次內(nèi)聯(lián)引入的資源緩存起來(lái)后續(xù)使用。緩存更新機(jī)制可以通過(guò)在 Cookie 中存放版本號(hào)來(lái)實(shí)現(xiàn)。這樣,服務(wù)端收到請(qǐng)求后,首先要檢查 Cookie 頭中的版本標(biāo)記:
由于 Cookie 內(nèi)容需要盡可能的少,所以一般只存總的版本號(hào)。這會(huì)導(dǎo)致頁(yè)面任何一處資源變動(dòng),都會(huì)改變總版本號(hào),進(jìn)而忽略客戶端所有 localStorage 緩存。要解決這個(gè)問(wèn)題可以繼續(xù)改進(jìn)我們的方案:Cookie 中只存放用戶唯一標(biāo)識(shí),用戶和資源對(duì)應(yīng)關(guān)系存在服務(wù)端。服務(wù)端收到請(qǐng)求后根據(jù)用戶標(biāo)識(shí),計(jì)算出哪些資源需要更新,從而輸出更有針對(duì)性的 HTML 文檔。
這套方案要投入實(shí)際使用,要處理一系列異常情況,例如 JS / Cookie / localStorage 被禁用;localStorage 被寫滿;localStorage 內(nèi)容損壞或丟失等等??紤]成本和實(shí)際收益,推薦只在移動(dòng)項(xiàng)目中使用這種方案。
對(duì)于 HTTP/2 來(lái)說(shuō),要解決前面這個(gè)問(wèn)題簡(jiǎn)直就太容易了,開啟「Server Push」即可。HTTP/2 的多路復(fù)用特性,使得可以在一個(gè)連接上同時(shí)打開多個(gè)流,雙向傳輸數(shù)據(jù)。Server Push,意味著服務(wù)端可以在發(fā)送頁(yè)面 HTML 時(shí)主動(dòng)推送其它資源,而不用等到瀏覽器解析到相應(yīng)位置,發(fā)起請(qǐng)求再響應(yīng)。另外,服務(wù)端主動(dòng)推送的資源不是被內(nèi)聯(lián)在頁(yè)面里,它們有自己獨(dú)立的 URL,可以被瀏覽器緩存,當(dāng)然也可以給其他頁(yè)面使用。
服務(wù)端可以主動(dòng)推送,客戶端也有權(quán)利選擇接收與否。如果服務(wù)端推送的資源已經(jīng)被瀏覽器緩存過(guò),瀏覽器可以通過(guò)發(fā)送?RST_STREAM
?幀來(lái)拒收。
可以看到,HTTP/2 的 Server Push 能夠很好地解決「如何讓重要資源盡快加載」這個(gè)問(wèn)題,一旦普及開來(lái),可以取代前面介紹過(guò)的 HTTP/1 時(shí)代優(yōu)化方案。
更多建議: