一個完整的 Web 請求到底發生了什麼

孤獨鍵客發表於2019-04-10

閱讀本文大概需要 7 分鐘。

一、從輸入一個網址開始

當我們在瀏覽器輸入一個網址,然後按下回車,接下來瀏覽器顯示了頁面。網速好的話這之間可能就一秒,但在這一秒內到底發生了什麼?

本文主要內容是試圖記錄一個完整 Web 請求的詳細過程,從使用者在瀏覽器中輸入 URL 地址說起,然後瀏覽器如何找到伺服器地址的過程,併發起請求;分析請求在達反向代理伺服器內部處理過程;最後到請求在伺服器端處理完成後,瀏覽器渲染響應頁面過程。

大致過程如下:

Web請求的工作原理可以簡單地歸納為:

    1. 瀏覽器通過 DNS 把域名解析成對應的IP地址;

    2. 根據這個 IP 地址在網際網路上找到對應的伺服器,建立 Socket 連線;

    3. 客戶端向伺服器傳送HTTP協議請求包,請求伺服器裡的資源文件;

    4. 在伺服器端,實際上還有複雜的業務邏輯:伺服器可能有多臺,到底指定哪臺伺服器處理請求,這需要一個負載均衡裝置來平均分配所有使用者的請求;

    5. 還有請求的資料是儲存在分散式快取裡還是一個靜態檔案中,或是在資料庫裡;

    6. 當資料返回瀏覽器時,瀏覽器解析資料發現還有一些靜態資源(如:css,js或者圖片)時又會發起另外的請求,而這些請求可能會在CDN上,那麼CDN伺服器又會處理這個使用者的請求。

    7. 客戶端與伺服器斷開。由客戶端解釋HTML文件,在客戶端螢幕上渲染圖形結果。

    一個 HTTP 事務就是這樣實現的,看起來很簡單,原理其實是挺負責的。需要注意的是客戶機與伺服器之間的通訊是非持久連線的,也就是當伺服器傳送了應答後就與客戶機斷開連線,等待下一次請求。

    但需要注意的是,從 HTTP 1.1 開始,伺服器可以與客戶端保持長連線,不一定是請求完成後就斷開連線,這取決於伺服器的操作。

    二、DNS 域名解析

    首先來看看最先發生的事情——DNS 域名解析,簡單的說就是把域名翻譯成 IP 地址。例如:把 www.test.com 這個域名翻譯成對應 IP 192.168.1.1,這裡只是舉個例子。

    如果你在瀏覽器中直接輸入的 IP 地址,那麼實際上會跳過這個步驟,否則會經理下面幾部:

    1、瀏覽器快取檢查

    瀏覽器會首先搜尋瀏覽器自身的 DNS 快取,快取時間比較短,大概只有1分鐘,且只能容納1000條快取,看自身的快取中是否有對應的條目,而且沒有過期,如果有且沒有過期則解析到此結束。

    2、作業系統快取檢查 + hosts 解析

    如果瀏覽器的快取裡沒有找到對應的條目,作業系統也會有一個域名解析的過程,那麼瀏覽器先搜尋作業系統的 DNS 快取中是否有這個域名對應的解析結果,如果找到且沒有過期則停止搜尋,解析到此結束。

    在 Linux 中可以通過 /etc/hosts 檔案來設定,可以將任何域名解析到任何能夠訪問的 IP 地址。如果在這裡指定了一個域名對應的 IP 地址,那麼瀏覽器會首先使用這個 IP 地址。當解析到這個配置檔案中的某個域名時,作業系統會在快取中快取這個解析結果,快取的時間同樣是受這個域名的失效時間和快取的空間大小控制的。

    3、本地區域名伺服器(Local DNS Server)解析

    如果在 hosts 檔案中也沒有找到對應的條目,瀏覽器會發起一個 DNS 的系統呼叫,會向本地配置的首選 DNS 伺服器發起域名解析請求(通過的是 UDP 協議向 DNS 的 53 埠發起請求,這個請求是遞迴的請求,也就是運營商的DNS伺服器必須得提供給我們該域名的IP地址)。

    在我們的網路配置中都會有“DNS 伺服器地址”這一項,這個地址就用於解決前面所說的如果兩個過程無法解析時要怎麼辦。作業系統會把這個域名傳送給這裡設定的 LDNS,也就是本地區的域名伺服器。

    這個 DNS 通常都提供給你本地網際網路接入的一個 DNS 解析服務,例如你是在學校接入網際網路,那麼你的 DNS 伺服器肯定在你的學校;如果你是在一個小區接入網際網路的,那這個 DNS 就是提供給你接入網際網路的應用提供商,即電信或者聯通。大約 80% 的域名解析都到這裡就已經完成了,所以 LDNS 主要承擔了域名的解析工作。

    4、根域名伺服器解析(Root Server)

    如果 LDNS 沒有找到對應的條目,則由運營商的 DNS 代我們的瀏覽器發起迭代 DNS 解析請求。它首先是會找根域的 DNS 的 IP 地址,找到根域的 DNS 地址,就會向其發起請求。然後根域名伺服器返回給本地域名伺服器一個所查詢域的主域名伺服器(gTLD Server)地址。

    5、主域名伺服器(gTLD Server)

    本地域名伺服器(LDNS Server)再向上一步返回的 gTLD 伺服器傳送請求。

    接受請求的 gTLD 伺服器查詢並返回此域名對應的 Name Server 域名伺服器的地址,這個 Name Server 通常就是你註冊的域名伺服器,例如你在某個域名服務提供商申請的域名,那麼這個域名解析任務就由這個域名提供商的伺服器來完成。

    Name Server 域名伺服器會查詢儲存的域名和IP的對映關係表,正常情況下都根據域名得到目標IP記錄,連同一個 TTL 值返回給 DNS Server 域名伺服器。

    下圖彙總了上面所說的 DNS 解析過程:

    三、TCP 的 3 次握手

    拿到域名對應的 IP 地址後,User-Agent(一般是指瀏覽器)會以一個隨機埠(1024 < 埠 < 65535)向伺服器的 WEB 程式發起 TCP 的連線請求。

    這裡還涉及 ARP(地址解析協議):是根據 IP 地址獲取實體地址 (MAC 地址) 的一個協議。
    當一個資料幀經過多次路由到達目的網路時,路由器只能知道其資料幀中的目的 IP 地址,而不知目標主機的硬體地址,網路層使用的是 IP地址,但是在實際網路鏈路上傳送資料幀時,最終必須使用該網路的硬體地址,此時需要目的主機的硬體地址,就要使用 ARP 來獲取到對應 IP 地址主機的實體地址。

    這個連線請求(原始的 Http 請求經過 TCP/IP 4層模型的層層封包)到達伺服器端後(這中間通過各種路由裝置,區域網內除外),進入到網路卡,然後是進入到核心的 TCP/IP 協議棧(用於識別該連線請求,解封包,一層一層的剝開),還有可能要經過Netfilter防火牆(屬於核心的模組)的過濾,最終到達WEB程式,最終建立了TCP/IP的連線。

    1. Client 首先傳送一個連線試探,SYN = 1 表示這是一個連線請求或連線接受報文,同時表示這個資料包不能攜帶資料,seq = x 表示 Client 自己的初始序號(seq = 0 就代表這是第 0 號包),這時候 Client 進入 syn_sent 狀態,表示客戶端等待伺服器的回覆。

    2. Server 監聽到連線請求報文後,如同意建立連線,則向 Client 傳送確認。報文中的 SYN 和 ACK 都置 1 ,ACK = x + 1 表示期望收到對方下一個報文段的第一個資料位元組序號是 x+1,同時表明 x 為止的所有資料都已正確收到(ACK = 1 其實是 ACK = 0 + 1,也就是期望客戶端的第 1 個包),seq = y 表示 Server 自己的初始序號(seq = 0 就代表這是伺服器這邊發出的第 0 號包)。這時伺服器進入 syn_rcvd,表示伺服器已經收到 Client 的連線請求,等待確認。

    3. Client 收到確認後還需再次傳送確認,同時攜帶要傳送給 Server 的資料。ACK 置 1 表示確認號 ack= y + 1 有效(代表期望收到伺服器的第 1 個包),Client自己的序號 seq= x + 1(表示這就是我的第1個包,相對於第0個包來說的),一旦收到Client的確認之後,這個TCP連線就進入 Established 狀態,就可以發起請求了。

    具體流程可以參看我之前的一篇文章《TCP、UDP、HTTP、HTTPS》。

    四、Nginx 反向代理

    1、反向代理

    反向代理(Reverse Proxy)方式是指:代理伺服器來接受 Internet 上的連線請求,然後將請求轉發給內部網路上的伺服器,並將從內部網路上伺服器得到的結果返回給 Internet 上請求連線的客戶端。此時代理伺服器對外就表現為一個伺服器,反向代理伺服器對於客戶端而言它就像是原始伺服器,並且客戶端不需要進行任何特別的設定。

    反向代理的作用:

    1. 保證內網的安全,可以使用反向代理提供 WAF 功能,阻止 web 攻擊。

    2. 負載均衡,通過反向代理伺服器來優化網站的負載。

    2、正向代理

    既然有反向代理,就肯定有正向代理。什麼叫正向代理呢?

    正向代理(Forward Proxy)通常都被簡稱為代理,就是在使用者無法正常訪問外部資源,可以通過代理的方式,讓使用者繞過防火牆,從而連線到目標網路或者服務。

    正向代理的工作原理就像一個跳板。

    比如:我訪問不了 google.com,但是我能訪問一個代理伺服器 A,A 能訪問 google.com,於是我先連上代理伺服器 A,告訴它我需要 google.com 的內容,A 就去取回來,然後返回給我。

    從網站的角度,只在代理伺服器來取內容的時候有一次記錄,有時候並不知道是使用者的請求,也隱藏了使用者的資料,這取決於代理告不告訴網站。

    正向代理是一個位於客戶端和原始伺服器(origin server)之間的伺服器。為了從原始伺服器取得內容,客戶端向代理髮送一個請求並指定目標(原始伺服器),然後代理向原始伺服器轉交請求並將獲得的內容返回給客戶端。

    3、正向代理與反向代理對比

    五、關閉 TCP 連線

    這一步不是所有的網頁都會這麼做,例如網頁版微信就沒有關閉 TCP 連線,因為微信上別人可以隨時發訊息給你,實際上別人先把訊息傳送到了微信伺服器,微信伺服器再通過 TCP 連結,把訊息推送到你的螢幕上。

    試想一下,如果網頁版微信關閉了 TCP 連線會怎樣?

    結果是:你不重新整理網頁,就永遠收不到訊息了。同時,如果你頻繁的發訊息給別人,那麼就在頻繁的建立連線,關閉連線,這是很消耗資源的。所以微信就乾脆不關閉 TCP 連線,這樣微信伺服器就可以給我們的瀏覽器發訊息。

    下圖是一次 Http 請求報文頭部資訊,其中 Connection: keep-alive 意味著這次請求結束後不會關閉 TCP 連線。

    當然不是所有的 HTTP 請求都沒有關閉連線,例如一篇博文,瀏覽器收到資料顯示就可以了,沒有那麼多動態資料,我看完就關了,這時就應該關閉 TCP 連線,當然這還是取決於請求的伺服器。說了這麼多,還沒說關閉連線。

    關閉 TCP 連線專業點說叫做“四次揮手”,與 TCP 建立連線的“三次握手”相對應。

    由於TCP連線是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的資料傳送任務後就能傳送一個 FIN 來終止這個方向的連線。收到一個 FIN 只意味著這一方向上沒有資料流動,一個TCP連線在收到一個 FIN 後仍能傳送資料。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。

    具體流程可以參看我之前的一篇文章《TCP、UDP、HTTP、HTTPS》。

    至此,一個 Web 請求的大致流程差不多就是這樣,東西還是挺多的,如果有不完善的地方,歡迎大家補充。

    參考連結
    https://www.jianshu.com/p/558455228c43

     

    ·END·

    程式設計師的成長之路

    路雖遠,行則必至

    微信ID:cxydczzl

     

    相關文章