HTTP 通訊簡介

從網址列按下 enter 後,到網頁畫面顯示出來,這中間發生了哪些事?

前言

我一直很喜歡的一個面試題,我決定今天把它拿出來告訴大家,歡迎參考,條件是如果有什麼有趣的回饋請一定要讓我知道!:P
每次面試工程師 (敝公司會接觸到的是 web 前後端以及 mobile app 工程師) 的時候,我幾乎都會問這個問題:「從網址列按下 enter 後,到網頁畫面顯示出來,這中間發生了哪些事?」這是一個好像很空泛卻又很實際的問題,我則是認為這個問題可以問出受試者對哪個領域較熟悉。
舉例來說,如果對方說了「瀏覽器會從 server 讀網頁跟 CSS 還有 JavaScript 回來,然後根據 HTML 產生 DOM,並根據 CSS 裡面描述的規則,對 DOM 中的元素進行排版,並且執行 JavaScript」這樣的話,那你可以猜測他應該比較熟悉前端。如果對方說的是「瀏覽器會送出 request 到 server,server 收到之後會先執行指定的 controller 裡面的 method,然後去存取資料庫把資料撈出來並顯示 view 回傳給瀏覽器」,那這個人應該曾經用某個後端的 framework 寫過網頁。又或是「server 會執行指定的 php 檔案,然後把執行結果顯示在瀏覽器上」,他可能是個比較資淺的 php 工程師 (因為講得很粗略)。我碰過一個有趣的例子,他跟我說的是「瀏覽器會先去找 DNS,解出主機的 IP…」,那表示他至少知道 DNS 這個東西XD。
總之,這個問題其實相當開放的,牽扯到的東西也滿多的,下面就來講講我會怎麼對付這個問題。

OSI 網路七層架構

現在的網路世界,可以被分成很多層,每一層各自有他的功能。參考維基百科上的 OSI 頁面,看出可分為 physical, data link, network, transport, session, presentation, application 等 layer。由於這個問題屬於較上層相關的問題,所以通常會傾向從 application layer 的角度回答,也就是關於 HTTP protocol 本身,以及處理 HTTP protocol 的應用程式等等。不過,由於網路本身也是很重要的,所以如果能夠從 SSL, TCP/IP, DNS, 防火牆等等角度去敘述,也會是個有趣的答案。

網路方面的回答

HTTP 一般走的是 TCP 的 port 80,HTTPS 一般是走 TCP port 443,有些 web server 可能會因為特殊原因開在 8000 或是 8080 等等,都滿常見的。不過其實走哪個 port 都可以,只要是 TCP 就好。(唔,其實我不知道 HTTP 能不能建構於 UDP 之上,我想技術上是可行的,但是有沒有大家共通的實作就是個問題了,所以這邊不討論)
如果提到了 TCP,那就可以進一步討論到 TCP 的 3-way handshaking,至少一般會了解到 SYN, SYN/ACK, ACK 這個過程。至於關閉連線的 RST…我承認我忘了 (這篇文章是在搭車的時候抽時間寫的,沒有網路就不查資料了XD),大致上是 RST, ACK + RST, ACK 之類的的吧…….
再底層有 IP 嘛,那就可以討論到像是 DNS 的解析 (DNS 用什麼 protocol 傳送資料呢?:P),拿到 IP 之後,資料要怎麼送出去?路由 (routing) 可能經過什麼地方?再更進一步,IP 的封包有沒有可能偽造來源?

關於 server 端的回答

由於應用層有很多部分可以討論,所以分開來講。server 上應該會有個 web server 去 listen port 80 (又跟 TCP 扯上關係了!),這個 server 可能是 apache、nginx、IIS 或是 lighttpd 之類的 general 的 web server,又或者是你的應用程式直接就自己 listen 80 port,比方說 rails 的 dev server: webrick,或是 thin, unicorn、php 的 php-fpm 等等。

Web Server

web server 是負責接受 http request 的。在 web server 的後面,需要有人負責處理 http request。有可能你的 web server 本身就具備處理 request 的能力,比方說裝了 mod_php 的 apache,或是有 passenger 的 nginx 等等。又或者 web server 只作為一個 reverse proxy,將 http request 轉送給知道怎麼處理的 server。轉送的方式可能透過某個 port,又或是某個 socket file (如果知道這個,應該是有管過或設定過 server 的人)。

Application Server

那麼 http request 要怎麼處理呢?由於 http request 就只是一串文字,包含了 header 跟 body (或說 content、payload,我不確定有沒有正式正確名稱),一般是將它解析、轉換成特殊格式,好讓後面的應用程式方便處理。這邊就會有些被定義好的界面,像是以前常聽到的 CGI、FastCGI、uWSGI、Rack…等等,而我們一般寫的 web 應用程式就是接上這些界面去實作,而不用自己 parse 純文字的 http protocol。
進入真正的 user 寫的 application 這邊,以我自身的經驗又可根據是否使用 framework 分成兩類。

不使用 framwork 的 application

不用 framework 的經典例子大概就是 php 了。PHP 因為以前 mod_php 安裝很方便 (至少我是這樣覺得啦),所以到現在依然很紅。幾乎所有 php 入門書都教你寫單純的 php 去產生網頁,簡單來說就是….連到哪個路徑就執行哪個檔案裡的程式碼,然後把印出來的結果傳回給 client 端。寫單純 php 的人,大概都用過 $_GET, $_POST 這些 global array,這些 array 的值就是 application server (或是 web server 的某個 module,如 mod_php) 幫你設定好,傳進來給你的 application 用的。最後印出來的資料 (比方說透過 echo "<p>這是一個 <marquee>php 網頁</marquee></p>" 印出來的) 也會交由他們負責回傳給 client。
(碎碎念:就是因為這些東西包裝得太好了,才會一堆人連 server-side 跟 client-side 的程式執行順序都搞不清楚…)

使用 framwork 的 application

用 framework 的話,狀況就複雜多了,程式的執行順序要看 framework 怎麼設計。以我接觸過的 framework 而言,會有一個單一進入點,request 進來之後,會先進入一個 router,不是網路上的路由器那個 router,是 URI router。URI router 的用途是根據 request 的 http method、URI (簡單說就是整串網址) (通常是這兩個) 決定要執行哪一段程式。以 (Web-)MVC framework 來說,就是某個特定的 controller 的某個特定的 method。或是像有些 framwork 的設計,讓你直接把 method、URI 跟 controller 寫在一起 (像是 Sinatra, Flask),也是還不賴這樣。又或是像 ASP.net WebForm 的設計,會直接讀取到路徑對應的 .aspx 檔案,然後把它 render 出來,並把程式部分的那些 handler 用奇妙的方式塞進 render 出來的 html 裡面…我是一直覺得這樣很亂我都被搞混啦XD
題外話:其實雖然現在公司寫 rails,我也不是完全清楚 rails 完整的執行流程細節…比方說 app server (unicorn) 進到 rails 的進入點是哪邊 (我只知道 config.ru 是 rack 的 initialize),他又是根據什麼去找到 routes.rb 這些的…
進到 controller 以後,就會….執行那個 method XD。不過如果是 rails developer 的話,可能會提到會先執行 ApplicationController 裡面寫的某些東西,或是會用到 before_action 之類的。當然 controller 中會去存取 model,然後 render view 之類的,這些就是各 framework 的設計不同的地方了…

Application Server 跟 Application 綁在一起的狀況

其實 application server 也不一定會跟 application 分開,比方說有些人可能喜歡自己處理 raw HTTP request,自己用 C 語言寫個程式,開了 socket 收文字資訊自己 parse…又或是像 node 世界,有些簡單的小應用就自己開 socket 自己處理資訊這樣 (但我不會寫 node,如果有誤解的話歡迎指教)。

關於 client 端的回答

Server 端講完了,來講講我比較不熟的 client 端 (又稱為前端、瀏覽器端)。前端嘛…就把 HTML 建成 DOM,然後 parse 完 CSS 把它裡面的規則拿來排版,最後再執行 JavaScript。(完)
….開玩笑的,不過這邊我比較組織不起來,可能有點雜亂。一開始瀏覽器會把 user 輸入的資訊 (網址、表單內容) 組合成 HTTP request,這個 request 是一個單純的文字串流,然後就透過 TCP 送出去了…經過 server 端的一番大戰之後,瀏覽器會收到一個回應 (http response),通常內容會是一份 html 資料啦,這時候瀏覽器就會開始讀取、解析這個 html。
HTML 中會有些 tag 參考到其他資料嘛,比方說像是 link (通常用來讀 css)、script (通常用來參考外部 JavaScript。VBScript?可以吃嗎?)、img (通常用來放圖片),當瀏覽器解析到這些外部資源的時候,就會再開一個連線,去讀取外部資源 (簡單來說意思是,下載那些檔案)。當瀏覽器讀到 js 檔案,或是一段 js 程式碼的時候,會停下解析 html 的工作,去執行 js。
瀏覽器會在解析的時候就執行 js 的這個事實滿重要的,因為很容易害人踩到雷XD。如果你把 js 放在 html 的 head 中,當你執行到這段 js 的時候,html 的 body 根本還沒有讀到,也就是說 DOM 根本還沒建立。如果在 js 中企圖去操作 DOM (比方說,想用 jQuery 找出某個 DOM element 並修改它),那會連找都找不到…這也就是為什麼很多 jQuery 範例都會要你用 $(function(){ /*...*/ }) 把要做的事情包裝起來。
再講個題外話,如果瀏覽器是透過 js 送出 request 的話,它應該是透過 XHR (XmlHttpRequest) 物件去送的,也就是俗稱的 AJAX request。這個狀況下,server 回的就可能不是 html 了。一般狀況可能會拿到一個 json 格式的資料,又或者是 xml 格式的資料,當然也有拿到 html 片段的 (像是 ASP.Net 的 UpdatePanel,就是個會去拿 HTML 片段回來顯示的玩意兒)。在處理上的話,就不會把焦點放在 rendering,而會放在 json/xml 格式的解析,還有針對 load 回來的資料的處理了。
對了,關於瀏覽器這邊,由於瀏覽器發展也是滿快的,我所理解的知識說不定已經過時了,也歡迎各方大德多多指教。<()>

關於 HTTP protocol 本身的回答

上面其實提到好幾次會扯到 HTTP protocol 的東西,像是 request/response,或是像資料格式之類的。有些人也許會注意到:既然瀏覽器送出資料,可能會拿到各種不同格式的資訊,那瀏覽器又是怎麼知道資料是什麼格式的呢?用「副檔名」嗎?
其實 HTTP 的 request/response 並不單純只是資料,而是還有一些 metadata 的,也就是 http 的 header (跟 html 的 <head> 不一樣噢!)。其實應該要用範例講比較清楚…可是我現在懶得貼範例 (攤)。
先來解釋 HTTP request header:關於 request 的 metadata 無非就是要取得什麼資料,主機名稱為何,接受什麼格式、編碼、語言的回應,還有接不接受回傳資料先壓縮過之類的。另外,如果有給 payload (最常見就是 POST 的時候),那還會有個欄位用來描述 payload 的長度 (byte 數)。
至於 response header,首先最重要的就是狀態碼。有在上網的人大概比較常聽到「404 Not Found」吧,那個 404 其實就是狀態碼。除此之外常見的還有像是「200 OK」、「302 Found」、「500 Internal Server Error」、「403 Forbidden」 、「400 Bad Request」之類的…反正很多啦XD 簡單分類:2xx 表示請求成功,3xx 表示重新導向、4xx 表示 (server 認為) client 端有錯誤,5xx 表示 server 自己有錯誤。除了狀態碼以外,header 剩下的部分差不多就是回應 request 裡面的要求了,比方說 Content-Type、Content-Encoding 之類的,還有壓縮與否之類的資訊。
對 http request/response 有興趣的話,只要打開瀏覽器的開發者工具 (Chrome, Firefox, Safari, IE 都有內建),選擇觀看網路流量,就可以看到 request/response 的內容了。

結語

其實寫了這麼亂一篇,還是覺得不夠全面,畢竟一開始我提出來的問題就是個開放式的問題…回頭看看這篇文章,發現如果是另一個人來看我的文章,也會抓出我對於哪些方面的技術接觸較多,哪些比較少,還有哪些地方其實我一竅不通XD
期許這篇文章可以對一些朋友有點幫助,也希望各路高手如果抓到什麼問題,或是覺得哪邊值得討論,都可以提出來。雖然我很懶 (XD 要不是搭車沒有網路用,我可能一輩子不會把這篇文章寫出來),但我還是會儘量把上面寫的東西修得更完整的啦….


Last modified on 2015-08-10