從輸入U(xiǎn)RL到頁(yè)面加載發(fā)生了什么,之前百度前端寫(xiě)過(guò)這篇文章,里面講解的非常的詳細(xì)。
總體來(lái)說(shuō)分為以下幾個(gè)過(guò)程:
- DNS解析
- TCP連接
- 發(fā)送HTTP請(qǐng)求
- 服務(wù)器處理請(qǐng)求并返回HTTP報(bào)文
- 瀏覽器解析渲染頁(yè)面
- 連接結(jié)束
DNS解析
DNS解析的過(guò)程就是尋找哪臺(tái)機(jī)器上有你需要資源的過(guò)程。當(dāng)你在瀏覽器中輸入一個(gè)地址時(shí),例如www.baidu.com 其實(shí)不是百度網(wǎng)站真正意義上的地址。互聯(lián)網(wǎng)上每一臺(tái)計(jì)算機(jī)的唯一標(biāo)識(shí)是它的IP地址,但是IP地址并不方便記憶。用戶更喜歡用方便記憶的網(wǎng)址去尋找互聯(lián)網(wǎng)上的其它計(jì)算機(jī),也就是上面提到的百度的網(wǎng)址。所以互聯(lián)網(wǎng)設(shè)計(jì)者需要在用戶的方便性與可用性方面做一個(gè)權(quán)衡,這個(gè)權(quán)衡就是一個(gè)網(wǎng)址到IP地址的轉(zhuǎn)換,這個(gè)過(guò)程就是DNS解析。它實(shí)際上充當(dāng)了一個(gè)翻譯的角色,實(shí)現(xiàn)了網(wǎng)址到IP地址的轉(zhuǎn)換。網(wǎng)址到IP地址轉(zhuǎn)換的過(guò)程是如何進(jìn)行的?
解析過(guò)程
DNS解析是一個(gè)遞歸查詢的過(guò)程。
上述圖片是查找www.google.com 的IP地址過(guò)程。首先在本地域名服務(wù)器中查詢IP地址,如果沒(méi)有找到的情況下,本地域名服務(wù)器會(huì)向根域名服務(wù)器發(fā)送一個(gè)請(qǐng)求,如果根域名服務(wù)器也不存在該域名時(shí),本地域名會(huì)向com頂級(jí)域名服務(wù)器發(fā)送一個(gè)請(qǐng)求,依次類(lèi)推下去。直到最后本地域名服務(wù)器得到google的IP地址并把它緩存到本地,供下次查詢使用。從上述過(guò)程中,可以看出網(wǎng)址的解析是一個(gè)從右向左的過(guò)程: com -> google.com -> www.google.com 但是你是否發(fā)現(xiàn)少了點(diǎn)什么,根域名服務(wù)器的解析過(guò)程呢?事實(shí)上,真正的網(wǎng)址是www.google.com. 并不是我多打了一個(gè).,這個(gè).對(duì)應(yīng)的就是根域名服務(wù)器,默認(rèn)情況下所有的網(wǎng)址的最后一位都是.,既然是默認(rèn)情況下,為了方便用戶,通常都會(huì)省略,瀏覽器在請(qǐng)求DNS的時(shí)候會(huì)自動(dòng)加上,所有網(wǎng)址真正的解析過(guò)程為: . -> .com -> google.com. -> www.google.com.。
DNS優(yōu)化
了解了DNS的過(guò)程,可以為我們帶來(lái)哪些?上文中請(qǐng)求到google的IP地址時(shí),經(jīng)歷了8個(gè)步驟,這個(gè)過(guò)程中存在多個(gè)請(qǐng)求(同時(shí)存在UDP和TCP請(qǐng)求,為什么有兩種請(qǐng)求方式,請(qǐng)自行查找)。如果每次都經(jīng)過(guò)這么多步驟,是否太耗時(shí)間?如何減少該過(guò)程的步驟呢?那就是DNS緩存。
DNS緩存
DNS存在著多級(jí)緩存,從離瀏覽器的距離排序的話,有以下幾種: 瀏覽器緩存,系統(tǒng)緩存,路由器緩存,IPS服務(wù)器緩存,根域名服務(wù)器緩存,頂級(jí)域名服務(wù)器緩存,主域名服務(wù)器緩存。
在你的chrome瀏覽器中輸入 chrome://dns/,你可以看到chrome瀏覽器的DNS緩存。
系統(tǒng)緩存主要存在/etc/hosts(Linux系統(tǒng))中
DNS負(fù)載均衡
不知道大家有沒(méi)有思考過(guò)一個(gè)問(wèn)題: DNS返回的IP地址是否每次都一樣?如果每次都一樣是否說(shuō)明你請(qǐng)求的資源都位于同一臺(tái)機(jī)器上面,那么這臺(tái)機(jī)器需要多高的性能和儲(chǔ)存才能滿足億萬(wàn)請(qǐng)求呢?其實(shí)真實(shí)的互聯(lián)網(wǎng)世界背后存在成千上百臺(tái)服務(wù)器,大型的網(wǎng)站甚至更多。但是在用戶的眼中,它需要的只是處理他的請(qǐng)求,哪臺(tái)機(jī)器處理請(qǐng)求并不重要。DNS可以返回一個(gè)合適的機(jī)器的IP給用戶,例如可以根據(jù)每臺(tái)機(jī)器的負(fù)載量,該機(jī)器離用戶地理位置的距離等等,這種過(guò)程就是DNS負(fù)載均衡,又叫做DNS重定向。大家耳熟能詳?shù)腃DN(Content Delivery Network)就是利用DNS的重定向技術(shù),DNS服務(wù)器會(huì)返回一個(gè)跟用戶最接近的點(diǎn)的IP地址給用戶,CDN節(jié)點(diǎn)的服務(wù)器負(fù)責(zé)響應(yīng)用戶的請(qǐng)求,提供所需的內(nèi)容。在這里打個(gè)免費(fèi)的廣告,我平時(shí)使用的比較多的是七牛云的CDN(免費(fèi))儲(chǔ)存圖片,作為我個(gè)人博客的圖床使用。
TCP連接
HTTP協(xié)議是使用TCP作為其傳輸層協(xié)議的,當(dāng)TCP出現(xiàn)瓶頸時(shí),HTTP也會(huì)受到影響。但由于TCP優(yōu)化這一塊我平常接觸的并不是很多,再加上大學(xué)時(shí)的計(jì)算機(jī)網(wǎng)絡(luò)的基礎(chǔ)基本上忘完,所以這一部分我也就不在這里分析了。
HTTPS協(xié)議
我不知道把HTTPS放在這個(gè)部分是否合適,但是放在這里好像又說(shuō)的過(guò)去。HTTP報(bào)文是包裹在TCP報(bào)文中發(fā)送的,服務(wù)器端收到TCP報(bào)文時(shí)會(huì)解包提取出HTTP報(bào)文。但是這個(gè)過(guò)程中存在一定的風(fēng)險(xiǎn),HTTP報(bào)文是明文,如果中間被截取的話會(huì)存在一些信息泄露的風(fēng)險(xiǎn)。那么在進(jìn)入TCP報(bào)文之前對(duì)HTTP做一次加密就可以解決這個(gè)問(wèn)題了。HTTPS協(xié)議的本質(zhì)就是HTTP + SSL(or TLS)。在HTTP報(bào)文進(jìn)入TCP報(bào)文之前,先使用SSL對(duì)HTTP報(bào)文進(jìn)行加密。從網(wǎng)絡(luò)的層級(jí)結(jié)構(gòu)看它位于HTTP協(xié)議與TCP協(xié)議之間。
HTTPS過(guò)程
HTTPS在傳輸數(shù)據(jù)之前需要客戶端與服務(wù)器進(jìn)行一個(gè)握手(TLS/SSL握手),在握手過(guò)程中將確立雙方加密傳輸數(shù)據(jù)的密碼信息。TLS/SSL使用了非對(duì)稱加密,對(duì)稱加密以及hash等。具體過(guò)程請(qǐng)參考經(jīng)典的阮一峰先生的博客TLS/SSL握手過(guò)程。
HTTPS相比于HTTP,雖然提供了安全保證,但是勢(shì)必會(huì)帶來(lái)一些時(shí)間上的損耗,如握手和加密等過(guò)程,是否使用HTTPS需要根據(jù)具體情況在安全和性能方面做出權(quán)衡。
HTTP請(qǐng)求
其實(shí)這部分又可以稱為前端工程師眼中的HTTP,它主要發(fā)生在客戶端。發(fā)送HTTP請(qǐng)求的過(guò)程就是構(gòu)建HTTP請(qǐng)求報(bào)文并通過(guò)TCP協(xié)議中發(fā)送到服務(wù)器指定端口(HTTP協(xié)議80/8080, HTTPS協(xié)議443)。HTTP請(qǐng)求報(bào)文是由三部分組成: 請(qǐng)求行, 請(qǐng)求報(bào)頭和請(qǐng)求正文。
請(qǐng)求行
格式如下:
Method Request-URL HTTP-Version CRLF
eg: GET index.html HTTP/1.1
常用的方法有: GET, POST, PUT, DELETE, OPTIONS, HEAD。
請(qǐng)求報(bào)頭
請(qǐng)求報(bào)頭允許客戶端向服務(wù)器傳遞請(qǐng)求的附加信息和客戶端自身的信息。
PS: 客戶端不一定特指瀏覽器,有時(shí)候也可使用Linux下的CURL命令以及HTTP客戶端測(cè)試工具等。
常見(jiàn)的請(qǐng)求報(bào)頭有: Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Type, Authorization, Cookie, User-Agent等。
上圖是使用Chrome開(kāi)發(fā)者工具截取的對(duì)百度的HTTP請(qǐng)求以及響應(yīng)報(bào)文,從圖中可以看出,請(qǐng)求報(bào)頭中使用了Accept, Accept-Encoding, Accept-Language, Cache-Control, Connection, Cookie等字段。Accept用于指定客戶端用于接受哪些類(lèi)型的信息,Accept-Encoding與Accept類(lèi)似,它用于指定接受的編碼方式。Connection設(shè)置為Keep-alive用于告訴客戶端本次HTTP請(qǐng)求結(jié)束之后并不需要關(guān)閉TCP連接,這樣可以使下次HTTP請(qǐng)求使用相同的TCP通道,節(jié)省TCP連接建立的時(shí)間。
請(qǐng)求正文
當(dāng)使用POST, PUT等方法時(shí),通常需要客戶端向服務(wù)器傳遞數(shù)據(jù)。這些數(shù)據(jù)就儲(chǔ)存在請(qǐng)求正文中。在請(qǐng)求包頭中有一些與請(qǐng)求正文相關(guān)的信息,例如: 現(xiàn)在的Web應(yīng)用通常采用Rest架構(gòu),請(qǐng)求的數(shù)據(jù)格式一般為json。這時(shí)就需要設(shè)置Content-Type: application/json。
服務(wù)器處理請(qǐng)求并返回HTTP報(bào)文
自然而然這部分對(duì)應(yīng)的就是后端工程師眼中的HTTP。后端從在固定的端口接收到TCP報(bào)文開(kāi)始,這一部分對(duì)應(yīng)于編程語(yǔ)言中的socket。它會(huì)對(duì)TCP連接進(jìn)行處理,對(duì)HTTP協(xié)議進(jìn)行解析,并按照?qǐng)?bào)文格式進(jìn)一步封裝成HTTP Request對(duì)象,供上層使用。這一部分工作一般是由Web服務(wù)器去進(jìn)行,我使用過(guò)的Web服務(wù)器有Tomcat, Jetty和Netty等等。
HTTP響應(yīng)報(bào)文也是由三部分組成: 狀態(tài)碼, 響應(yīng)報(bào)頭和響應(yīng)報(bào)文。
狀態(tài)碼
狀態(tài)碼是由3位數(shù)組成,第一個(gè)數(shù)字定義了響應(yīng)的類(lèi)別,且有五種可能取值:
- 1xx:指示信息–表示請(qǐng)求已接收,繼續(xù)處理。
- 2xx:成功–表示請(qǐng)求已被成功接收、理解、接受。
- 3xx:重定向–要完成請(qǐng)求必須進(jìn)行更進(jìn)一步的操作。
- 4xx:客戶端錯(cuò)誤–請(qǐng)求有語(yǔ)法錯(cuò)誤或請(qǐng)求無(wú)法實(shí)現(xiàn)。
- 5xx:服務(wù)器端錯(cuò)誤–服務(wù)器未能實(shí)現(xiàn)合法的請(qǐng)求。平時(shí)遇到比較常見(jiàn)的狀態(tài)碼有:200, 204, 301, 302, 304, 400, 401, 403, 404, 422, 500(分別表示什么請(qǐng)自行查找)。
響應(yīng)報(bào)頭
常見(jiàn)的響應(yīng)報(bào)頭字段有: Server, Connection...。
響應(yīng)報(bào)文
服務(wù)器返回給瀏覽器的文本信息,通常HTML, CSS, JS, 圖片等文件就放在這一部分。
瀏覽器解析渲染頁(yè)面
瀏覽器在收到HTML,CSS,JS文件后,它是如何把頁(yè)面呈現(xiàn)到屏幕上的?下圖對(duì)應(yīng)的就是WebKit渲染的過(guò)程
瀏覽器是一個(gè)邊解析邊渲染的過(guò)程。首先瀏覽器解析HTML文件構(gòu)建DOM樹(shù),然后解析CSS文件構(gòu)建渲染樹(shù),等到渲染樹(shù)構(gòu)建完成后,瀏覽器開(kāi)始布局渲染樹(shù)并將其繪制到屏幕上。這個(gè)過(guò)程比較復(fù)雜,涉及到兩個(gè)概念: reflow(回流)和repain(重繪)。DOM節(jié)點(diǎn)中的各個(gè)元素都是以盒模型的形式存在,這些都需要瀏覽器去計(jì)算其位置和大小等,這個(gè)過(guò)程稱為relow;當(dāng)盒模型的位置,大小以及其他屬性,如顏色,字體,等確定下來(lái)之后,瀏覽器便開(kāi)始繪制內(nèi)容,這個(gè)過(guò)程稱為repain。頁(yè)面在首次加載時(shí)必然會(huì)經(jīng)歷reflow和repain。reflow和repain過(guò)程是非常消耗性能的,尤其是在移動(dòng)設(shè)備上,它會(huì)破壞用戶體驗(yàn),有時(shí)會(huì)造成頁(yè)面卡頓。所以我們應(yīng)該盡可能少的減少reflow和repain。
JS的解析是由瀏覽器中的JS解析引擎完成的。JS是單線程運(yùn)行,也就是說(shuō),在同一個(gè)時(shí)間內(nèi)只能做一件事,所有的任務(wù)都需要排隊(duì),前一個(gè)任務(wù)結(jié)束,后一個(gè)任務(wù)才能開(kāi)始。但是又存在某些任務(wù)比較耗時(shí),如IO讀寫(xiě)等,所以需要一種機(jī)制可以先執(zhí)行排在后面的任務(wù),這就是:同步任務(wù)(synchronous)和異步任務(wù)(asynchronous)。JS的執(zhí)行機(jī)制就可以看做是一個(gè)主線程加上一個(gè)任務(wù)隊(duì)列(task queue)。同步任務(wù)就是放在主線程上執(zhí)行的任務(wù),異步任務(wù)是放在任務(wù)隊(duì)列中的任務(wù)。所有的同步任務(wù)在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧;異步任務(wù)有了運(yùn)行結(jié)果就會(huì)在任務(wù)隊(duì)列中放置一個(gè)事件;腳本運(yùn)行時(shí)先依次運(yùn)行執(zhí)行棧,然后會(huì)從任務(wù)隊(duì)列里提取事件,運(yùn)行任務(wù)隊(duì)列中的任務(wù),這個(gè)過(guò)程是不斷重復(fù)的,所以又叫做事件循環(huán)(Event loop)。
瀏覽器在解析過(guò)程中,如果遇到請(qǐng)求外部資源時(shí),如圖像,iconfont,JS等。瀏覽器將重復(fù)1-6過(guò)程下載該資源。請(qǐng)求過(guò)程是異步的,并不會(huì)影響HTML文檔進(jìn)行加載,但是當(dāng)文檔加載過(guò)程中遇到JS文件,HTML文檔會(huì)掛起渲染過(guò)程,不僅要等到文檔中JS文件加載完畢還要等待解析執(zhí)行完畢,才會(huì)繼續(xù)HTML的渲染過(guò)程。原因是因?yàn)镴S有可能修改DOM結(jié)構(gòu),這就意味著JS執(zhí)行完成前,后續(xù)所有資源的下載是沒(méi)有必要的,這就是JS阻塞后續(xù)資源下載的根本原因。CSS文件的加載不影響JS文件的加載,但是卻影響JS文件的執(zhí)行。JS代碼執(zhí)行前瀏覽器必須保證CSS文件已經(jīng)下載并加載完畢。