輸入一個URL到頁面呈現其中發生的過程-------http過程詳解

在咱們點擊一個網址,到它可以呈如今瀏覽器中,展現在咱們面前,這個過程當中,電腦裏,網絡上,究竟發生了什麼事情。php

服務器啓動監聽模式

那咱們就開始了,故事其實並非從在瀏覽器的地址欄輸入一個網址,或者咱們抓着鼠標點擊一個連接開始,事情的開端要追溯到服務器啓動監聽服務的時候,在某個未知的時刻,一臺機房裏普普統統的刀片服務器,加上電,啓動了操做系統,隨着操做系統的就緒,服務器啓動了 http 服務進程,這個 http 服務的守護進程,(daemon),多是 apache,也多是 nginx,無論怎麼說,這個 http 服務進程開始定位到服務器上的 www 文件夾,通常是位於 /var/www ,而後啓動了一些附屬的模塊,例如 php,或者,使用 fastcgi 方式鏈接到 php 的 fpm 管理進程,而後,向操做系統申請了一個 tcp 鏈接,而後綁定在了 80 端口,調用了 accept 函數,開始了默默的監聽,監聽着可能來自位於地球任何一個地方的請求,隨時準備作出響應。css

這個時候,典型的狀況下,機房裏面應該還有一個數據庫服務器,或許,還有一臺緩存服務器,若是對於流量巨大的網站,那麼動態腳本的解釋器可能還有單獨的物理機器來跑,若是是中小的站點,那麼,上述的各色服務,甚至均可能在一臺物理機上,這些服務監聽之間的關係。無論怎麼說,他們作好了準備,靜候差遣。html

客戶端瀏覽器發送請求

1. 解析URL

2. 輸入的是 URL 仍是搜索的關鍵字?

當協議或主機名不合法時,瀏覽器會將地址欄中輸入的文字傳給默認的搜索引擎。大部分狀況下,在把文字傳遞給搜索引擎的時候,URL會帶有特定的一串字符,用來告訴搜索引擎此次搜索來自這個特定瀏覽器。mysql

3. 轉換非 ASCII 的 Unicode 字符

  • 瀏覽器檢查輸入是否含有不是 a-z, A-Z,0-9, - 或者 . 的字符
  • 這裏主機名是 google.com ,因此沒有非ASCII的字符;若是有的話,瀏覽器會對主機名部分使用 Punycode 編碼nginx

    4. 檢查 HSTS 列表

  • 瀏覽器檢查自帶的「預加載 HSTS(HTTP嚴格傳輸安全)」列表,這個列表裏包含了那些請求瀏覽器只使用HTTPS進行鏈接的網站算法

  • 若是網站在這個列表裏,瀏覽器會使用 HTTPS 而不是 HTTP 協議,不然,最初的請求會使用HTTP協議發送
  • 注意,一個網站哪怕不在 HSTS 列表裏,也能夠要求瀏覽器對本身使用 HSTS 政策進行訪問。瀏覽器向網站發出第一個 HTTP 請求以後,網站會返回瀏覽器一個響應,請求瀏覽器只使用 HTTPS 發送請求。然而,就是這第一個 HTTP 請求,卻可能會使用戶收到 downgrade attack 的威脅,這也是爲何現代瀏覽器都預置了 HSTS 列表。

5. dns查詢

  • 瀏覽器緩存查詢:瀏覽器檢查域名是否在緩存當中(要查看 Chrome 當中的緩存, 打開 chrome://net-internals/#dns)。
  • 本地host查詢:若是緩存中沒有,就去調用 gethostbyname 庫函數(操做系統不一樣函數也不一樣)進行查詢,gethostbyname 函數在試圖進行DNS解析以前首先檢查域名是否在本地 Hosts 裏,Hosts 的位置 不一樣的操做系統有所不一樣
  • 發送DNS 查詢請求:若是 gethostbyname 沒有這個域名的緩存記錄,也沒有在 hosts 裏找到,它將會向 DNS 服務器發送一條 DNS 查詢請求。DNS 服務器是由網絡通訊棧提供的,一般是本地路由器或者 ISP 的緩存 DNS 服務器。
  • 查詢本地 DNS 服務器
  • 若是 DNS 服務器和咱們的主機在同一個子網內,系統會按照下面的 ARP 過程對 DNS 服務器進行 ARP查詢
  • 若是 DNS 服務器和咱們的主機在不一樣的子網,系統會按照下面的 ARP 過程對默認網關進行查詢

6. ARP 過程

要想發送 ARP(地址解析協議)廣播,咱們須要有一個目標 IP 地址,同時還須要知道用於發送 ARP 廣播的接口的 MAC 地址。sql

  • 首先查詢 ARP 緩存,若是緩存命中,咱們返回結果:目標 IP = MAC
    若是緩存沒有命中:chrome

  • 查看路由表,看看目標 IP 地址是否是在本地路由表中的某個子網內。是的話,使用跟那個子網相連的接口,不然使用與默認網關相連的接口。數據庫

  • 查詢選擇的網絡接口的 MAC 地址

咱們發送一個二層( OSI 模型 中的數據鏈路層)ARP 請求:apache

ARP Request:

1
2
3
4
Sender MAC: interface:mac:address:here
Sender IP: interface.ip.goes.here
Target MAC: FF:FF:FF:FF:FF:FF (Broadcast)
Target IP: target.ip.goes.here

根據鏈接主機和路由器的硬件類型不一樣,能夠分爲如下幾種狀況:

  1. 直連:
  • 若是咱們和路由器是直接鏈接的,路由器會返回一個 ARP Reply (見下面)。
  1. 集線器:
  • 若是咱們鏈接到一個集線器,集線器會把 ARP 請求向全部其它端口廣播,若是路由器也「鏈接」在其中,它會返回一個 ARP Reply 。
    交換機:

  • 若是咱們鏈接到了一個交換機,交換機會檢查本地 CAM/MAC 表,看看哪一個端口有咱們要找的那個 MAC 地址,若是沒有找到,交換機會向全部其它端口廣播這個 ARP 請求。

  • 若是交換機的 MAC/CAM 表中有對應的條目,交換機會向有咱們想要查詢的 MAC 地址的那個端口發送 ARP 請求
  • 若是路由器也「鏈接」在其中,它會返回一個 ARP Reply

ARP Reply:

1
2
3
4
Sender MAC: target:mac:address:here
Sender IP: target.ip.goes.here
Target MAC: interface:mac:address:here
Target IP: interface.ip.goes.here

如今咱們有了 DNS 服務器或者默認網關的 IP 地址,咱們能夠繼續 DNS 請求了:

  • 使用 53 端口向 DNS 服務器發送 UDP 請求包,若是響應包太大,會使用 TCP 協議
  • 若是本地/ISP DNS 服務器沒有找到結果,它會發送一個遞歸查詢請求,一層一層向高層 DNS 服務器作查詢,直到查詢到起始受權機構,若是找到會把結果返回

    7. 使用套接字創建三次握手

當瀏覽器獲得了目標服務器的 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地址,把它封裝成一個TCP packet。
  • 這個 TCP packet 接下來會進入鏈路層,鏈路層會在封包中加入 frame頭部,裏面包含了本地內置網卡的MAC地址以及網關(本地路由器)的 MAC 地址。像前面說的同樣,若是內核不知道網關的 MAC 地址,它必須進行 ARP 廣播來查詢其地址。

到了如今,TCP 封包已經準備好了,能夠進行tcp三次握手(在地址解析完畢後首先進行的是三次握手,就是客戶端和服務器端只發送SYN包,創建三次鏈接後,在對HTTP的引用進行響應)

最終封包會到達管理本地子網的路由器。在那裏出發,它會繼續通過自治區域(autonomous system, 縮寫 AS)的邊界路由器,其餘自治區域,最終到達目標服務器。一路上通過的這些路由器會從IP數據報頭部裏提取出目標地址,並將封包正確地路由到下一個目的地。IP數據報頭部 time to live (TTL) 域的值每通過一個路由器就減1,若是封包的TTL變爲0,或者路由器因爲網絡擁堵等緣由封包隊列滿了,那麼這個包會被路由器丟棄。

8. TLS 握手

  • 客戶端發送一個 Client hello 消息到服務器端,消息中同時包含了它的 Transport Layer Security (TLS) 版本,可用的加密算法和壓縮算法。

  • 服務器端向客戶端返回一個 Server hello 消息,消息中包含了服務器端的TLS版本,服務器選擇了哪一個加密和壓縮算法,以及服務器的公開證書,證書中包含了公鑰。客戶端會使用這個公鑰加密接下來的握手過程,直到協商生成一個新的對稱密鑰

  • 客戶端根據本身的信任CA列表,驗證服務器端的證書是否有效。若是有效,客戶端會生成一串僞隨機數,使用服務器的公鑰加密它。這串隨機數會被用於生成新的對稱密鑰
  • 服務器端使用本身的私鑰解密上面提到的隨機數,而後使用這串隨機數生成本身的對稱主密鑰
  • 客戶端發送一個 Finished 消息給服務器端,使用對稱密鑰加密此次通信的一個散列值
  • 服務器端生成本身的 hash 值,而後解密客戶端發送來的信息,檢查這兩個值是否對應。若是對應,就向客戶端發送一個 Finished 消息,也使用協商好的對稱密鑰加密
  • 從如今開始,接下來整個 TLS 會話都使用對稱祕鑰進行加密,傳輸應用層(HTTP)內容

9. HTTP 服務器請求處理

HTTPD(HTTP Daemon)在服務器端處理請求/響應。最多見的 HTTPD 有 Linux 上經常使用的 Apache 和 nginx,以及 Windows 上的 IIS。

  • HTTPD 接收請求
  • 服務器把請求拆分爲如下幾個參數:
    • HTTP 請求方法(GET, POST, HEAD, PUT, DELETE, CONNECT, OPTIONS, 或者 TRACE)。直接在地址欄中輸入 URL 這種狀況下,使用的是 GET 方法
    • 域名:google.com
    • 請求路徑/頁面:/ (咱們沒有請求google.com下的指定的頁面,所以 / 是默認的路徑)
  • 服務器驗證其上已經配置了 google.com 的虛擬主機
  • 服務器驗證 google.com 接受 GET 方法
  • 服務器驗證該用戶可使用 GET 方法(根據 IP 地址,身份信息等)
  • 若是服務器安裝了 URL 重寫模塊(例如 Apache 的 mod_rewrite 和 IIS 的 URL Rewrite),服務器會嘗試匹配重寫規則,若是匹配上的話,服務器會按照規則重寫這個請求
  • 服務器根據請求信息獲取相應的響應內容,這種狀況下因爲訪問路徑是 「/「 ,會訪問首頁文件(你能夠重寫這個規則,可是這個是最經常使用的)。
  • 服務器會使用指定的處理程序分析處理這個文件,假如 Google 使用 PHP,服務器會使用 PHP 解析 index 文件,並捕獲輸出,把 PHP 的輸出結果返回給請求者

請求進入處理函數以後,若是客戶端所請求須要瀏覽的內容是一個動態的內容,那麼處理函數會相應的從數據源裏面取出數據,這個地方通常會有一個緩存,例如 memcached 來減少 db 的壓力,若是引入了 orm 框架的話,那麼處理函數直接向 orm 框架索要數據就能夠了,由 orm 框架來決定是使用內存裏面的緩存仍是從 db 去取數據,通常緩存都會有一個過時的時間,而 orm 框架也會在取到數據回來以後,把數據存一份在內存緩存中的。

orm 框架負責把面向對象的請求翻譯成標準的 sql 語句,而後送到後端的 db 去執行,db 這裏以 mysql 爲例的話,那麼一條 sql 進來以後,db 自己也是有緩存的,不過 db 的緩存通常是用 sql 語言 hash 來存取的,也就是說,想要緩存可以命中,除了查詢的字段和方法要同樣之外,查詢的參數也要徹底如出一轍纔可以使用 db 自己的查詢緩存,sql 通過查詢緩存器,而後就會到達查詢分析器,在這裏,db 會根據被搜索的數據表的索引創建狀況,和 sql 語言自己的特色,來決定使用哪個字段的索引,值得一提的是,即便一個數據表同時在多個字段創建了索引,可是對於一條 sql 語句來講,仍是隻能使用一個索引,因此這裏就須要分析使用哪一個索引效率最高了,通常來講,sql 優化在這個點上也是很重要的一個方面。

sql 由 db 返回結果集後,再由 orm 框架把結果轉換成模型對象,而後由 orm 框架進行一些邏輯處理,把準備好的數據,送到視圖層的渲染引擎去渲染,渲染引擎負責模板的管理,字段的友好顯示,也包括負責一些多國語言之類的任務。對於一條請求在 mvc 中的生命週期,在視圖層把頁面準備好後,再從動態腳本解釋器送回到 http 服務器,由 http 服務器把這些正文加上一個響應頭,封裝成一個標準的 http 響應包,再經過 tcp ip 協議,送回到客戶機瀏覽器。

10. 客戶端渲染

  1. 判斷http響應狀態碼
  2. 編碼解析
  3. 構建dom樹
  4. 根據css樣式和dom樹,構建渲染樹
    歷經千辛萬苦,咱們請求的響應終於到達了客戶端的瀏覽器,響應到達瀏覽器以後,瀏覽器首先判斷狀態碼,若是是 200 開頭的就好辦,直接進入渲染流程,若是是 300 開頭的就要去相應頭裏面找 location 域,根據這個 location 的指引,進行跳轉,這裏跳轉須要開啓一個跳轉計數器,是爲了不兩個或者多個頁面之間造成的循環的跳轉,當跳轉次數過多以後,瀏覽器會報錯,同時中止。若是是 400 開頭或者 500 開頭的狀態碼,瀏覽器也會給出一個錯誤頁面。

當瀏覽獲得一個正確的 200 響應以後,接下來面臨的一個問題就是多國語言的編碼解析了,響應頭是一個 ascii 的標準字符集的文本,這個還好辦,可是響應的正文本質上就是一個字節流,對於這一坨字節流,瀏覽器要怎麼去處理呢,首先瀏覽器會去看響應頭裏面指定的 encoding 域,若是有了這個東西,那麼就按照指定的 encoding 去解析字符,若是沒有的話,那麼瀏覽器會使用一些比較智能的方式,去猜想和判斷這一坨字節流應該使用什麼字符集去解碼。相關的筆記能夠看這裏,瀏覽器對編碼的肯定

解決了字符集的問題,接下來就是構建 dom 樹了,在 html 語言嵌套正常並且規範的狀況下,這種 xml 標記的語言是比較容易的可以構建出一棵 dom 樹出來的,固然,對於互聯網上大量的不規範的頁面,不一樣的瀏覽器應該有本身不一樣的容錯去處理。構建出來的 dom 本質上仍是一棵抽象的邏輯樹,構建 dom 樹的過程當中,若是遇到了由 script 標籤包起來的 js 動態腳本代碼,那麼會把代碼送到 js 引擎裏面去跑,若是遇到了 style 標籤包圍起來的 css 代碼,也會保存下來,用於稍後的渲染。若是遇到了 img 等引用外部文件的標籤,那麼瀏覽器會根據指定的 url 再次發起一個新的 http 請求,去把這個文件拉取回來,值得一提的是,對於同一個域名下的下載過程來講,瀏覽器通常容許的併發請求是有限的,一般控制在兩個左右,因此若是有不少的圖片的話,通常出於優化的目的,都會把這些圖片使用一臺靜態文件的服務器來保存起來,負責響應,從而減小主服務器的壓力。

dom 樹構造好了以後,就是根據 dom 樹和 css 樣式表來構造 render 樹了,這個纔是真正的用於渲染到頁面上的一個一個的矩形框的樹,對於 render 樹上每個框,須要肯定他的 x y 座標,尺寸,邊框,字體,形態,等等諸多方面的東西,render 樹一旦構建完成,整個頁面也就準備好了,能夠上菜了。

須要說明的是,下載頁面,構建 dom 樹,構建 render 樹這三個步驟,實際上並非嚴格的前後順序的,爲了加快速度,提升效率,讓用戶不要等那麼久,如今通常都並行的往前推動的,現代的瀏覽器都是一邊下載,下載到了一點數據就開始構建 dom 樹,也一邊開始構建 render 樹,構建了一點就顯示一點出來,這樣用戶看起來就不用等待那麼久了。

當服務器提供了資源以後(HTML,CSS,JS,圖片等),瀏覽器會執行下面的操做:

  • 解析 —— HTML,CSS,JS
  • 渲染 —— 構建 DOM 樹 -> 渲染 -> 佈局 -> 繪製

    11. 瀏覽器

瀏覽器的功能是從服務器上取回你想要的資源,而後展現在瀏覽器窗口當中。資源一般是 HTML 文件,也多是 PDF,圖片,或者其餘類型的內容。資源的位置經過用戶提供的 URI(Uniform Resource Identifier) 來肯定。

瀏覽器解釋和展現 HTML 文件的方法,在 HTML 和 CSS 的標準中有詳細介紹。這些標準由 Web 標準組織 W3C(World Wide Web Consortium) 維護。

不一樣瀏覽器的用戶界面大都十分接近,有不少共同的 UI 元素:

  • 一個地址欄
  • 後退和前進按鈕
  • 書籤選項
  • 刷新和中止按鈕
  • 主頁按鈕
  • 瀏覽器高層架構

組成瀏覽器的組件有:

  • 用戶界面:用戶界面包含了地址欄,前進後退按鈕,書籤菜單等等,除了請求頁面以外全部你看到的內容都是用戶界面的一部分
  • 瀏覽器引擎:瀏覽器引擎負責讓 UI 和渲染引擎協調工做
  • 渲染引擎:渲染引擎負責展現請求內容。若是請求的內容是 HTML,渲染引擎會解析 HTML 和 CSS,而後將內容展現在屏幕上
  • 網絡組件:網絡組件負責網絡調用,例如 HTTP 請求等,使用一個平臺無關接口,下層是針對不一樣平臺的具體實現
  • UI後端:UI 後端用於繪製基本 UI 組件,例以下拉列表框和窗口。UI 後端暴露一個統一的平臺無關的接口,下層使用操做系統的 UI 方法實現
  • Javascript 引擎:Javascript 引擎用於解析和執行 Javascript 代碼
  • 數據存儲 數據存儲組件是一個持久層。瀏覽器可能須要在本地存儲各類各樣的數據,例如 Cookie 等。瀏覽器也須要支持諸如 localStorage,IndexedDB,WebSQL 和 FileSystem 之類的存儲機制

    12. HTML 解析

瀏覽器渲染引擎從網絡層取得請求的文檔,通常狀況下文檔會分紅8kB大小的分塊傳輸。

HTML 解析器的主要工做是對 HTML 文檔進行解析,生成解析樹。

解析樹是以 DOM 元素以及屬性爲節點的樹。DOM是文檔對象模型(Document Object Model)的縮寫,它是 HTML 文檔的對象表示,同時也是 HTML 元素面向外部(如Javascript)的接口。樹的根部是」Document」對象。整個 DOM 和 HTML 文檔幾乎是一對一的關係。

解析算法

HTML不能使用常見的自頂向下或自底向上方法來進行分析。主要緣由有如下幾點:

  • 語言自己的「寬容」特性
  • HTML 自己多是殘缺的,對於常見的殘缺,瀏覽器須要有傳統的容錯機制來支持它們
  • 解析過程須要反覆。對於其餘語言來講,源碼不會在解析過程當中發生變化,可是對於 HTML 來講,動態代碼,例如腳本元素中包含的 document.write() 方法會在源碼中添加內容,也就是說,解析過程實際上會改變輸入的內容

因爲不能使用經常使用的解析技術,瀏覽器創造了專門用於解析 HTML 的解析器。解析算法在 HTML5 標準規範中有詳細介紹,算法主要包含了兩個階段:標記化(tokenization)和樹的構建。

解析結束以後

瀏覽器開始加載網頁的外部資源(CSS,圖像,Javascript 文件等)。

此時瀏覽器把文檔標記爲「可交互的」(interactive),瀏覽器開始解析處於「推遲」模式的腳本(defer屬性的腳本文件),也就是那些須要在文檔解析完畢以後再執行的腳本。以後文檔的狀態會變爲「完成」(complete,DOMContentLoaded事件被響應),瀏覽器會進行「加載」事件(onload事件被響應)。

注意解析 HTML 網頁時永遠不會出現「語法錯誤」,瀏覽器會修復全部錯誤,而後繼續解析。

執行同步 Javascript 代碼。

相關文章
相關標籤/搜索