從點一個 URL 到看到頁面中間發生了什麼事?(上集)

那天在網路上看到這個題目,覺得非常有趣,決定以我現在的視野來寫寫我的回答,設個明年今天的提醒,期許一年後的我再度看到這篇時,能夠給出一個更清晰的答案。

這題的有趣之處是,任何人都回答得出來,但你對網頁知識的瞭解程度會清楚展現在你的回答中。你可以拆三步,花一分鐘講完,也可以為其中每個發生的事情寫成一系列叢書,端看你對網頁的知識到哪裡(還有時間夠不夠)。如果想自己挑戰看看的,可以先擬出一個回答,再去偉哉網路上看看大神們都如何接招或是繼續看下去。

我把我的回答切成四大塊:

一、瀏覽器收到 URL,找到對應的 Web Server,發送 HTTP 請求
二、Web Server 接到請求,將請求送到應用程式
三、應用程式處理請求,打包成 HTTP 回應
四、瀏覽器收到回應,呈現頁面

前兩節放在上集,後兩節放下集。在這個回答中有牽涉到網頁應用程式的部分(第二、三節)我會聚焦在一個以一個 Rails 框架寫成的應用程式作為例子。

另外,這篇文章有部分(好啦其實是所有)段落會有中英夾雜的情況,原因是有些專有名詞平常溝通時就不會使用翻譯,硬要用中文反而顯得奇怪,但是基本上這裡每個專有名詞都會同時放上中文與英文,避免翻譯不一樣導致無法理解。

開始吧!


一、瀏覽器收到 URL,找到對應的網域,發送 HTTP 請求

關鍵字:DNS, CDN, TCP, HTTP

請求流向:URL → DNS 找 IP → TCP 連線 → 發送 HTTP 請求

網域名稱系統 Domain Name Server (DNS)

瀏覽器一收到 URL,首先會利用 DNS (Domain Name Server),將人類可辨識的、以英文與數字組成的網址,轉換成機器可辨識的,以純數字組成的 IP 位址(像是 google.com 的其中一個 IP 位址是 172.217.160.78)。可以將 DNS 想像成一本超大型戶口名簿,我們輸入的網址是姓名,是人類稱呼彼此的方式,而 IP 位址是身分證字號,獨一無二,是電腦識別身份的方式。

在查找 DNS 的過程,會經過一個四階層的快取(瀏覽器 → 操作系統 → 操作系統中 hosts 文件 → LDNS),加速查找過程。

內容傳遞網路 Content Delivery Network (CDN)

A CDN allows for the quick transfer of assets needed for loading Internet content including HTML pages, javascript files, stylesheets, images, and videos.

節錄自 CloudFlare

CDN 的主要存在目的,是確保頁面載入時間,解決訪問延遲的問題。當今天一個台灣的使用者發送請求到一個主機位置在美國的網站 A,他就必須要連線去美國取得資料,美國的伺服器再把資料傳回台灣,這樣一來一往中間傳輸的成本就會反映在網頁載入的速度。所以當一個網站服務量大,想要確保從世界各地的使用者連線到網站都能夠有同樣的速度,CDN 就可以登場了。

CDN 的運作原理是,在世界各個點設立伺服器,利用快取將原本主機上的資料複製到每個伺服器,而當使用者要連線時,會自動抓離使用者最近的那個快取伺服器來連線。假設今天網站 A 用 CDN 在日本的伺服器存了一份檔案,那麼台灣的使用者要連線到 A 時,就會去找日本的伺服器,不用大老遠跑到美國去撈資料了。

如果網站有使用 CDN 服務的話,則一個 URL 的 IP 位址會有很多個,在這個步驟中會依照發送請求的位置、網路流量、負載狀況等等決定要導流到哪一個 IP 位址(哪一個伺服器)。

TCP 協議與 HTTP 協議

找到 IP 位址後,瀏覽器利用 TCP 協議 跟 web server 建立連線(俗稱握手),再根據 URL 的內容產生 http 請求。TCP 連線的標頭中會包含 source port 與destination port,一般來說,destination port 默認的值是 80,意思是網頁伺服器通常會監聽 port 80 來接收 http 請求,而 source port 則會隨著使用者的瀏覽器作業系統有所變化。當我們使用 localhost:3000,我們就是打開 port 3000 來監聽請求。

一個典型的 HTTP 請求包含 header(標頭), action(方法,本例中用 GET ), body(內容),有興趣的可以看看 Mozilla 的 Http Basics 這篇文章。


二、Web Server 接到請求,將請求送到應用程式

關鍵字:Middleware, Web Server, Application Server, Rack

請求流向: (load balance) → web server → application server → application

在介紹這些名詞之前,先來介紹一下中介軟體。

中介軟體 Middleware

The purpose of middleware is to provide high level abstraction that you can use to easily bind together the functionality that allows your web application to receive, and then respond to client requests.

節錄自 Web Application Development: Basic Concepts

中介軟體原本是用在分散式系統(distributed systems)中。分散式系統是一群電腦透過網路互相傳遞訊息的系統,簡言之就是多台電腦一起合作出任務,FreeCodeCamp 有一篇詳細的介紹,很長但值得一看。這群電腦合作的過程中,必定要有一些方法才能保持個體間的合作順暢,而這就是中介軟體的工作。

這裡中介軟體做的事情,是接受 http 請求,把請求交給應用程式的 scripts/services、處理請求、打包成回應後送回客戶端。因為有了中介軟體,開發人員不需要去煩惱有關 web server, app server 這些設定,只要專心把程式碼寫好就好了。MVC 架構其實就是建立在這些中介軟體上,讓我們的程式碼能夠與 web server 溝通,所以也可以將中介軟體想像成客戶端與伺服器端的橋樑(the dash in client-server)。

在這樣的定義下,這一節所做的事,其實都是中介軟體,以下分別介紹。

負載均衡 Load Balance

負載均衡其實就是分流。今天當一台 server 不能負荷請求的數量,加開了第二台、第三 server 之後,就要開始思考如何將請求分配給這些機器。有幾種方式能做到,其中一種是反向代理(reverse proxy server)

反向代理的原理,可以想像一個場景:今天到餐廳用餐,你向服務生點了一道紅蘿蔔炒蛋炒飯,十五分鐘後服務生把你要的紅蘿蔔炒蛋炒飯給你。你不知道服務生把點菜單送進廚房後發生了什麼事,或實際上是誰炒了你的紅蘿蔔炒蛋炒飯,你唯一能掌握的是服務生的樣子,還有你桌上的紅蘿蔔炒蛋炒飯。

因此,反向代理除了能夠實現負載均衡,同時還有許多功能,像是提升網路安全性,或是像 CDN 也是一個反向代理的延伸應用。常見的反向代理軟體包含 Nginx, Apache HTTP server 與 HAProxy。

網頁伺服器 Web Server

網頁伺服器 主要的功能是接受並且回應用戶端請求的網頁靜態內容。用戶端十之八九是瀏覽器或是手機應用程式,而請求與回應都是以 HTTP 的形式。

翻譯節錄自 Nginx 官網

其實網頁伺服器有兩個意思,一個是可以處理 HTTP 請求的伺服器(似乎有人直接叫它 server 以作區分),一個是提供網頁的伺服器程式。總之,每一台 server 都會最少執行一個 web server。

一般常見的網頁伺服器供應商有 Apache 與 Nginx 。

應用程式伺服器 Application Server

應用程式伺服器是一種提供一個應用程式執行環境的軟體框架。

翻譯節錄自維基百科

白話地說,就是一個能夠讓你寫的程式碼可以跑起來的框架。 在 Rails 中常見的 app server 選項有 WEBrick, Unicorn, Thin, Puma, Passenger,現在 Puma 是 Rails 預設的伺服器。很多 app server 也會同時提供負載均衡、故障轉移等功能,讓開發者夠專注在商業邏輯。

Rack,Rails 的中介軟體管理者

A Rack application is a Ruby object (not a class) that responds to call. It takes exactly one argument, the environment and returns an Array of exactly three values: The status, the headers, and the body.

摘錄自 RubyDoc

Rack 是中介軟體中的 middleware framework。Middleware framework 是連接網頁框架 (web application framework) 與應用程式伺服器 (application server) 的工具。基本上網頁開發人員不會需要動到這裏。而 Rails 中的 middleware framework 就叫做 Rack。

Rack 把 Rails 中一系列 middleware components (通常用 Ruby 寫成)用 stack 堆疊起來,使開發者便於操作。要看 Rails 裡面有裝了哪些中介軟體,在應用程式資料夾的終端機上打 $rake middleware就可以看到了。當我們打 $rails server,我們實際上做的事情是建立了一個 Rack::Server,連接到 app server (在 Rails 5 中沒有特別指名的話就是預設的 puma),並且呼叫那一系列 middleware 來與 app server 合作。

根據 Rack 的定義,我們其實可以實作一個 Rack middleware,像是這個範例

如果你確實在終端機打了 $rake middleware,你會在最後一行看到

run [your app name]::Application.routes

代表這個請求在經過一系列中介軟體,最後被送到 Rails 的路由器,由此便進入 MVC 的闖關世界了!所以下一篇,將會從路由開始。