從輸入URL到頁面載入發生了什麼?(個人閱讀筆記筆記)
- DNS解析
- TCP連線
- 發生HTTP報文
- 伺服器處理請求並返回HTTP報文
- 瀏覽器解析渲染頁面
- 連線結束
DNS解析
當你在位址列輸入https://www.csdn.net/,這其實不是網站真實的地址,網際網路上每一臺計算機的唯一標識是它的IP地址,但是IP地址並不方便記憶。需要通過DNS解析,查詢網站真實的IP地址。一個網址到IP地址的轉換,這個過程就是DNS解析。 一般瀏覽器先檢查域名是否在快取中,(要檢視 Chrome 當中的快取, 開啟 chrome://net-internals/#dns)。接下來,呼叫 gethostbyname 庫函式,檢查域名是否在本地Hosts裡,若兩者都沒有,計算機會向DNS伺服器傳送查詢DNS請求。 查詢過程如圖:
DNS負載均衡 不知道大家有沒有思考過一個問題: DNS返回的IP地址是否每次都一樣?如果每次都一樣是否說明你請求的資源都位於同一臺機器上面,那麼這臺機器需要多高的效能和儲存才能滿足億萬請求呢?其實真實的網際網路世界背後存在成千上百臺伺服器,大型的網站甚至更多。但是在使用者的眼中,它需要的只是處理他的請求,哪臺機器處理請求並不重要。DNS可以返回一個合適的機器的IP給使用者,例如可以根據每臺機器的負載量,該機器離使用者地理位置的距離等等,這種過程就是DNS負載均衡,又叫做DNS重定向。大家耳熟能詳的CDN(Content Delivery Network)就是利用DNS的重定向技術,DNS伺服器會返回一個跟使用者最接近的點的IP地址給使用者,CDN節點的伺服器負責響應使用者的請求,提供所需的內容。
TCP連線
HTTP協議是使用TCP作為其傳輸層協議的,當TCP出現瓶頸時,HTTP也會受到影響。HTTP請求是包裹為TCP請求傳輸的,伺服器端收到TCP報文時會解包提取出HTTP報文。
當瀏覽器得到了目標伺服器的 IP 地址,以及 URL 中給出來埠號(http 協議預設埠號是 80, https 預設埠號是 443),它會呼叫系統庫函式 socket ,請求一個 TCP流套接字,對應的引數是 AF_INET/AF_INET6 和 SOCK_STREAM 。
這個請求首先被交給傳輸層,在傳輸層請求被封裝成 TCP segment。目標埠會被加入頭部,源埠會在系統核心的動態埠範圍內選取(Linux下是ip_local_port_range) TCP segment 被送往網路層,網路層會在其中再加入一個 IP 頭部,裡面包含了目標伺服器的IP地址以及本機的IP地址,把它封裝成一個IP packet。 這個 TCP packet 接下來會進入鏈路層,鏈路層會在封包中加入 frame 頭部,裡面包含了本地內建網絡卡的MAC地址以及閘道器(本地路由器)的 MAC 地址。像前面說的一樣,如果核心不知道閘道器的 MAC 地址,它必須進行 ARP 廣播來查詢其地址。
HTTP報文是明文,如果中間被擷取的話會存在一些資訊洩露的風險。那麼在進入TCP報文之前對HTTP做一次加密就可以解決這個問題了。**HTTPS協議的本質就是HTTP + SSL(or TLS)。**在HTTP報文進入TCP報文之前,先使用SSL對HTTP報文進行加密。從網路的層級結構看它位於HTTP協議與TCP協議之間。 TLS 握手 客戶端傳送一個 ClientHello 訊息到伺服器端,訊息中同時包含了它的 Transport Layer Security (TLS) 版本,可用的加密演算法和壓縮演算法。 伺服器端向客戶端返回一個 ServerHello 訊息,訊息中包含了伺服器端的TLS版本,伺服器所選擇的加密和壓縮演算法,以及數字證書認證機構(Certificate Authority,縮寫 CA)簽發的伺服器公開證書,證書中包含了公鑰。客戶端會使用這個公鑰加密接下來的握手過程,直到協商生成一個新的對稱金鑰 客戶端根據自己的信任CA列表,驗證伺服器端的證書是否可信。如果認為可信,客戶端會生成一串偽隨機數,使用伺服器的公鑰加密它。這串隨機數會被用於生成新的對稱金鑰 伺服器端使用自己的私鑰解密上面提到的隨機數,然後使用這串隨機數生成自己的對稱主金鑰 客戶端傳送一個 Finished 訊息給伺服器端,使用對稱金鑰加密這次通訊的一個雜湊值 伺服器端生成自己的 hash 值,然後解密客戶端傳送來的資訊,檢查這兩個值是否對應。如果對應,就向客戶端傳送一個 Finished 訊息,也使用協商好的對稱金鑰加密 從現在開始,接下來整個 TLS 會話都使用對稱祕鑰進行加密,傳輸應用層(HTTP)內容。
發生HTTP報文
其實這部分又可以稱為前端工程師眼中的HTTP,它主要發生在客戶端。傳送HTTP請求的過程就是構建HTTP請求報文並通過TCP協議中傳送到伺服器指定埠(HTTP協議80/8080, HTTPS協議443)。HTTP請求報文是由三部分組成: 請求行, 請求報頭和請求正文。 請求行 格式如下: Method Request-URL HTTP-Version CRLF
eg: GET index.html HTTP/1.1 常用的方法有: GET, POST, PUT, DELETE, OPTIONS, HEAD。
問題:GET和POST請求有什麼不同?分別在哪些場景使用。
請求報頭 請求報頭允許客戶端向伺服器傳遞請求的附加資訊和客戶端自身的資訊。 PS: 客戶端不一定特指瀏覽器,有時候也可使用Linux下的CURL命令以及HTTP客戶端測試工具等。 常見的請求報頭有: Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Type, Authorization, Cookie, User-Agent等。 補充: 很多爬蟲通過偽裝User-Agent,達到爬取目的。
上圖是使用Chrome開發者工具擷取的對百度的HTTP請求以及響應報文,從圖中可以看出,請求報頭中使用了Accept, Accept-Encoding, Accept-Language, Cache-Control, Connection, Cookie等欄位。Accept用於指定客戶端用於接受哪些型別的資訊,Accept-Encoding與Accept類似,它用於指定接受的編碼方式。Connection設定為Keep-alive用於告訴客戶端本次HTTP請求結束之後並不需要關閉TCP連線,這樣可以使下次HTTP請求使用相同的TCP通道,節省TCP連線建立的時間。 請求正文 當使用POST, PUT等方法時,通常需要客戶端向伺服器傳遞資料。這些資料就儲存在請求正文中。在請求包頭中有一些與請求正文相關的資訊,例如: 現在的Web應用通常採用Rest架構,請求的資料格式一般為json。這時就需要設定Content-Type: application/json。 伺服器處理請求並返回HTTP報文 自然而然這部分對應的就是後端工程師眼中的HTTP。後端從在固定的埠接收到TCP報文開始,這一部分對應於程式語言中的socket。它會對TCP連線進行處理,對HTTP協議進行解析,並按照報文格式進一步封裝成HTTP Request物件,供上層使用。這一部分工作一般是由Web伺服器去進行,我使用過的Web伺服器有Apache和Nginx等等。
HTTP響應報文也是由三部分組成: 狀態碼, 響應報頭和響應報文。 狀態碼 狀態碼是由3位陣列成,第一個數字定義了響應的類別,且有五種可能取值:
1xx:指示資訊–表示請求已接收,繼續處理。
2xx:成功–表示請求已被成功接收、理解、接受。
3xx:重定向–要完成請求必須進行更進一步的操作。
4xx:客戶端錯誤–請求有語法錯誤或請求無法實現。
5xx:伺服器端錯誤–伺服器未能實現合法的請求。 平時遇到比較常見的狀態碼有:200, 204, 301, 302, 304, 400, 401, 403, 404, 422, 500(分別表示什麼請自行查 問題: 301和302有什麼區別?
HTTP快取
響應報頭 常見的響應報頭欄位有: Server, Connection…。
響應報文 伺服器返回給瀏覽器的文字資訊,通常HTML, CSS, JS, 圖片等檔案就放在這一部分。
瀏覽器解析渲染頁面 瀏覽器在收到HTML,CSS,JS檔案後,它是如何把頁面呈現到螢幕上的?下圖對應的就是WebKit渲染的過程。
瀏覽器是一個邊解析邊渲染的過程。首先瀏覽器解析HTML檔案構建DOM樹,然後解析CSS檔案構建渲染樹,等到渲染樹構建完成後,瀏覽器開始佈局渲染樹並將其繪製到螢幕上。這個過程比較複雜,涉及到兩個概念: reflow(迴流)和repain(重繪)。DOM節點中的各個元素都是以盒模型的形式存在,這些都需要瀏覽器去計算其位置和大小等,這個過程稱為reflow;當盒模型的位置,大小以及其他屬性,如顏色,字型,等確定下來之後,瀏覽器便開始繪製內容,這個過程稱為repain。頁面在首次載入時必然會經歷reflow和repaint。reflow和repaint過程是非常消耗效能的,尤其是在移動裝置上,它會破壞使用者體驗,有時會造成頁面卡頓。所以我們應該儘可能少的減少reflow和repaint。
JS的解析是由瀏覽器中的JS解析引擎完成的。JS是單執行緒執行,也就是說,在同一個時間內只能做一件事,所有的任務都需要排隊,前一個任務結束,後一個任務才能開始。但是又存在某些任務比較耗時,如IO讀寫等,所以需要一種機制可以先執行排在後面的任務,這就是:同步任務(synchronous)和非同步任務(asynchronous)。JS的執行機制就可以看做是一個主執行緒加上一個任務佇列(task queue)。同步任務就是放在主執行緒上執行的任務,非同步任務是放在任務佇列中的任務。所有的同步任務在主執行緒上執行,形成一個執行棧;非同步任務有了執行結果就會在任務佇列中放置一個事件;指令碼執行時先依次執行執行棧,然後會從任務佇列裡提取事件,執行任務佇列中的任務,這個過程是不斷重複的,所以又叫做事件迴圈(Event loop)。
瀏覽器在解析過程中,如果遇到請求外部資源時,如影象,iconfont,JS等。瀏覽器將重複1-6過程下載該資源。請求過程是非同步的,並不會影響HTML文件進行載入,但是當文件載入過程中遇到JS檔案,HTML文件會掛起渲染過程,不僅要等到文件中JS檔案載入完畢還要等待解析執行完畢,才會繼續HTML的渲染過程。原**因是因為JS有可能修改DOM結構,這就意味著JS執行完成前,後續所有資源的下載是沒有必要的,這就是JS阻塞後續資源下載的根本原因。**CSS檔案的載入不影響JS檔案的載入,但是卻影響JS檔案的執行。JS程式碼執行前瀏覽器必須保證CSS檔案已經下載並載入完畢。(這也就是為什麼JS指令碼要在後面引用)