# Computer Networking — 2.2 The Web and HTTP contributed by <[`kaeteyaruyo`](https://github.com/kaeteyaruyo)> ###### tags: `Computer Networking` 直到 1990 年代早期,網際網路主要的使用者都是研究者、學術人員,和大學生。他們的需求不外乎就是遠端登入主機、在本地和遠端之間傳輸檔案、收發新聞和電子郵件,這樣而已。雖然這些服務時至今日還是相當有用,不過網際網路在那時候,基本上除了學術圈以外沒有任何人聽過。直到 1990 年代初期,一個超大型新人出現了,那就是全球資訊網 [Berners-Lee 1994]。全球資訊網(以下簡稱 Web)是第一個引起一般大眾注意的網際網路應用服務,它戲劇性地轉變了所有人在工作環境內外的互動方式。他將網際網路從眾多資料網路的其中一個,提升為最重要且獨一無二的資料網路。 網際網路最引人入勝的特色應該就是它可以隨我們的需要提供服務 (*on demand*)。不像傳統的電視或是廣播,使用者會被限制收看/收聽節目的時間,網際網路讓使用者可以在任何他們想要的時候取得任何他們想看的東西。除了這個隨選隨用的特色之外,網際網路還讓任何一個人都可以用超低的成本來發佈內容到網路上。超連結和搜尋引擎讓我們可以在網路的資訊之海中查詢資料,圖片和影片刺激我們的感官,而表單、JavaScript、Java applets,和其他各式各樣的裝置讓我們可以跟這些網站互動。網頁以及他所使用的協定也成了如 YouTube、網頁電子郵件(如 Gmail)和各種行動服務(如 Instagram 和 Google Map)的供應平台。 ## 2.2.1 Overview of HTTP **超文本傳輸協定 (HyperText Transfer Protocol, HTTP)**,Web 所使用的應用層協定,是 Web 的心臟地帶。他的細節定義在 [RFC 1945] 和 [RFC 2616] 中。HTTP 需要被實作在兩支程式上,也就是客戶端和伺服器,這兩支程式會在兩個不同終端裝置上交換 HTTP 訊息。HTTP 定義了這些訊息的結構還有雙方要怎麼交換訊息。在開始解釋 HTTP 的運作原理前,我們要先來學習一些 HTTP 的用語。 * **網頁 (Web page)**,又叫文件 (document),是由許多物件組成的。 * **物件 (Object)** 是一個簡單的檔案,像是 HTML 頁面、一張 JPEG 圖片、一個 Java applet,或是一段影片之類的,每個物件都可以用一個 URL 來定址。 * 大部分的網頁都是由一個 **base HTML file** 和多個被參照的物件組成的。例如:如果有一個網頁是由一個 HTML 檔案和五張照片組成的,那他就有六個物件,其中該 HTML 檔案就是 base HTML file。 * Base HTML file 藉由 **URL** 來參照其他物件。每個 URL 都含有兩個部份:主機名稱 (host name) 和檔案路徑 (path name)。例如: ``` http://www.someSchool.edu/someDepartment/picture.gif ``` 在這個網址中,`www.someSchool.edu` 就是主機名稱,`/someDepartment/picture.gif` 就是檔案路徑。 * **網頁瀏覽器 (Web browser)** 實作了 HTTP 的客戶端,所以在往後的說明中,當我們會交替著使用*瀏覽器*和*客戶端*這兩個詞,在我們討論 Web 的時候這兩個字是同義的。 * **網頁伺服器 (Web server)** 實作了 HTTP 的伺服器端,用來存放物件。知名的網頁伺服器包含 Apache 和 Microsoft Internet Information Server 等。 HTTP 定義了客戶端要怎麼跟伺服器要網頁,還有伺服器要怎麼回傳網頁給客戶端,細節我們稍後會提到,但目前我們先在 Figure 2.6 裏面描繪出這個過程的大概的樣貌。簡單來說,當使用者(透過點擊超連結之類的方式)請求了一個網頁,瀏覽器就會送出一個 HTTP 請求的訊息給伺服器,去要這個網頁中的所有物件。伺服器收到了這個請求,就會發送一個回應訊息給瀏覽器,回應中會夾帶著使用者所要求的網頁物件。 ![](https://hackmd.io/_uploads/r1nhZzUah.png) > Figure 2.6 HTTP 的請求-回應 (request-response) 行為 HTTP 使用 TCP 作為底下的傳輸層協定。HTTP 客戶端必須要先建立起和伺服器端的連線,才能開始交換 HTTP 訊息。就像我們在 [2.1](https://hackmd.io/@kaeteyaruyo/computer-networking-2-1) 節中提到的,傳輸雙方會藉由插座介面來存取 TCP 的功能,HTTP 訊息會先流經插座,進到傳輸層穿越網路後,流到收信方的插座,然後才送到收信方的程式中。一旦送信方將訊息傳了出去,這個訊息要怎麼傳遞就歸 TCP 管了,這意味著每一個 HTTP 訊息一旦送出,就絕對會送到收信方程式的手中。在此我們可以觀察到這個分層架構的好處: HTTP 不需要擔心資料遺失問題,也不用管 TCP 是怎麼處理丟包問題或重組封包順序的,因為這都是 TCP 還有更底下的協定的工作。 有一件重要的事情你需要知道:網頁伺服器在回應網頁給客戶端的時候,是不會去記住任何跟客戶端有關的狀態資訊的。如果有某一個客戶端在短時間之內要了同一個網頁好幾次,伺服器並不會說「喔你很煩捏我剛剛不是才送這個檔案給你的嗎」,而是會老老實實的再把同一個網頁回應回去,因為它根本不記得自己剛剛做了什麼。因為 HTTP 伺服器這種不維護客戶端資訊的特性,我們稱 HTTP 是一種**無狀態的協定 (stateless protocol)**。另一個重要的事,就是 HTTP 是採用主從式架構的應用。網頁伺服器會持續提供服務、擁有固定的 IP,並且負責處理來自可能多達上百萬個不同瀏覽器的請求。 ## 2.2.2 Non-Persistent and Persistent Connections 在許多網路服務中,客戶端和伺服器端的通訊是會持續一段很長的時間的,傳輸雙方之間可能會有多次的請求和回應往來。依據不同的應用,這些請求可能是一個接著一個、間隔固定時間的傳出,也有可能是斷斷續續的、間歇性的傳出。當客戶端和伺服器的溝通是在 TCP 之上進行的,開發者就會面臨一個重要的選擇:這一來一往的訊息到底應該要分別用*不同的* TCP 連線來傳輸,還是通通用*同一個* TCP 連線來傳輸就好了呢?如果是前者,那我們就說這種應用使用的是**非持續性的連線 (non-persistent connections)**;如果是後者,那就是**持續性的連線 (persistent connections)**。為了更深入了解這個設計議題,我們可以觀察在特定的應用情境下(也就是 HTTP 拉),這兩種設計分別有什麼好處跟壞處。在 HTTP 中,這兩種設計都可以使用,雖然預設會使用的是持續性連線模式,但 HTTP 客戶端或伺服器也是可以被設定成使用非持續性連線的。 ### HTTP with Non-Persistent Connections 讓我們從頭到尾看過一次網頁是怎麼在非持續性連線模式下從伺服器被傳送到客戶端上的。假設有一個網頁是由一個 base HTML file 和 10 個圖片組成的,這 11 個物件都放在同一台主機上,且 base HTML file 的網址是: ``` http://www.someSchool.edu/someDepartment/home.index ``` 那麼整個傳輸過程就會是: 1. HTTP 客戶端的程式會要求對 `www.someSchool.edu` 這個主機發起一個 TCP 連線,並且指定埠號為 80,這是 HTTP 協定的預設埠號。在 TCP 連線中,客戶端和伺服器端都會有一個對應的網路插座。 2. HTTP 客戶端會透過插座送出一個 HTTP 請求訊息到伺服器端,這個請求會包含 `/someDepartment/home.index` 這個路徑名稱在裡面(關於 HTTP 訊息,我們等等會詳談)。 3. HTTP 伺服器藉由他的插座收到了這個 HTTP 訊息後,他就會(從 RAM 或是硬碟中)撈出 `/someDepartment/home.index` 這個物件、把這個物件封裝成 HTTP 回應訊息的格式,然後藉由他的插座送出這個訊息回去給客戶端。 4. HTTP 伺服器的程式會叫 TCP 關閉 TCP 連線(但是 TCP 不會馬上關閉連線,他要等到確定客戶端真的有確實收到訊息才會關)。 5. HTTP 客戶端收到了回應訊息。TCP 連線關閉。訊息會顯示被封裝起來的物件是一個 HTML 檔案,接著客戶端會從回應訊息中把這個檔案抽出來,檢查一下這個 HTML 檔案,在當中尋找那 10 個圖片物件的參照(也就是網址)。 6. 重複 1-4 以取得每一個被參照的圖片。 一旦瀏覽器取得了網頁檔案,他就會自己想辦法把這個網站顯示給使用者看。不同的瀏覽器可能會用不同的實作方式來顯示網頁,但無論網站是怎麼被顯示的,這都跟 HTTP 沒有任何關係。HTTP 的規格([RFC 1945] 和 [RFC 2616])裡只有定義了 HTTP 客戶端和伺服器之間的傳輸協定而已。 在上述的非持續性連線的例子中,每傳完一個物件,該傳輸用的 TCP 連線就會被關閉,每一個 TCP 連線都只會傳恰好一個請求和一個回應。像這樣如果有 11 個檔案要傳,就會有 11 個 TCP 連線被建立。 不過我們上面描述的步驟中,我們並沒有說清楚這 10 張照片到底是透過 10 個依序建立的 TCP 連線傳送的,還是由 10 個同時建立的平行的 TCP 連線傳送的。確實,現今的瀏覽器大多允許使用者自行設定平行化的程度,一般來說預設會允許 5-10 個 TCP 連線平行運作。但如果使用者喜歡,他們也可以把平行化的連線數量上限設定成 1,那這 10 個連線就會依序建立了。我們在下一章當中會提到,使用平行化連線可以縮短回應時間。 但在我們繼續往下看之前,我們先來隨便估算一下從客戶端要求 base HTML file 開始到整個完整的網頁都要完,到底需要多少時間。為此我們定義一個名詞,叫作**往返時間 (round-trip time, RTT)**,代表一個小封包從客戶端送出、抵達伺服器端,再從伺服器端跑回來總共所需的時間。RTT 包含了傳遞延遲、在每個路由器和交換器的排隊延遲,還有封包處理延遲(這些我們在 [1.4](https://hackmd.io/@kaeteyaruyo/computer-networking-1-4) 節中都討論過了)。現在讓我們來想想當使用者按了一下超連結之後會發生什麼事:如 Figure 2.7 所示,瀏覽器為了建立一個 TCP 連線,他需要跟伺服器進行「三向交握」 —— 瀏覽器會先送出一個小封包給伺服器,伺服器收到後會回送一個 ACK 回去給瀏覽器,接著瀏覽器會把對網頁的請求跟著第三段的 ACK 一起丟去伺服器,最後伺服器就會把網頁給丟回去客戶端。三向交握的前兩個階段,算做一個 RTT,然後第三段的請求到網頁送回客戶端的第一個瞬間,算做第二個 RTT,而檔案傳輸本身也需要花一點時間。所以估算起來,整個網頁傳輸過程大約需要花兩個 RTT 外加網頁本身的傳輸時間。 ![](https://hackmd.io/_uploads/Hki2UrL63.png) > Figure 2.7 大概估算一下從網頁的請求到傳送完畢需要花多少時間 ### HTTP with Persistent Connections 非持續性連線有幾個明顯的缺點。首先,每次要傳一個物件就必須要建立一個全新的 TCP 連線,要知道建立 TCP 連線,電腦就需要要一塊記憶體空間來當作緩衝區,還需要維護 TCP 連線的參數,這對網頁伺服器來說可能會造成非常大的負擔,因為它可能會需要同時維護上百個與不同瀏覽器之間的連線。再來,就像我們剛剛計算的,每一個物件傳輸都需要耗費兩個 RTT,一個是為了建立 TCP 連線,另一個是為了請求和傳遞物件。 HTTP 1.1 採用了持續性連線,也就是說,伺服器在回應完第一個物件請求之後,不會馬上把跟該客戶端之間的連線切斷,因此接下來從同一個客戶端傳來的所有請求都可以用同一個 TCP 連線進行傳輸,如此一來就可以在一個連線之內傳送完整個網頁(和其中的其他物件)。更好的是,如果同一個客戶端接下來還要求了放在同一個伺服器上的其他網頁,也都可以順便一起傳一傳。這些請求可以一個接著一個地傳,不需要等前一個請求有回覆了才傳下一個(這種作法稱作管線化 (pipelining))。一般來說,伺服器會在連線過了一段時間都沒有動靜之後就把連線關掉,這個 timeout 的時長是可以設定的。最近,HTTP/2 [RFC 7540] 基於 HTTP 1.1 做改良,允許多個請求和回覆可以在同一個連線當中互相穿插,並且加入了在連線中給這些 HTTP 訊息排列優先次序的機制。我們會在第二章和第三章的作業當中量化地比較非持續性連線和持續性連線的效能。我們也很推薦你去閱讀 [Heidemann 1997; Nielsen 1997; RFC 7540] 以了解更多細節。 ## 2.2.3 HTTP Message Format HTTP 的規格書中 [RFC 1945; RFC 2616; RFC 7540] 包含了 HTTP 訊息格式的定義。HTTP 訊息有兩種:請求訊息和回應訊息。我們接著會來分別討論這兩者。 ### HTTP Request Message 以下是一個典型的 HTTP 請求訊息: ``` GET /somedir/page.html HTTP/1.1 Host: www.someschool.edu Connection: close User-agent: Mozilla/5.0 Accept-language: fr ``` 雖然這訊息看起來很簡單,但他已經可以讓我們學到很多東西。首先,這個訊息是用一般的 ASCII 文字寫的,所以就算你只是個懂電腦的普通人類 (XDD) 也可以輕易看懂。接著,我們可以注意到這條訊息包含了五行文字,每一行的最後都有一個換行,最後一行的後面還有再多一個換行。雖然上面這條訊息有五行,但其實也可以有更多行或是最少只有一行。其中第一行被叫作**請求行 (request line)**,而剩下的其他行叫作**標頭行 (header lines)**。請求行有 3 個欄位:方法欄位、URL 欄位,以及 HTTP 版本號欄位。方法欄位可以是以下幾種值之一:`GET`, `POST`, `HEAD`, `PUT`, 和 `DELETE`。最常用的是 `GET`,用在瀏覽器要請求一個物件的時候,而他請求的物件就會由 URL 欄位寫明。在上面例子中,這個瀏覽器要請求的物件就是 `/somedir/page.html`。版本號欄位應該就不用多說了吧,在這個例子中,該瀏覽器使用的 HTTP 版本是 HTTP/1.1。 接著我們來看看這些標頭行: * `Host: www.someschool.edu`: 這行說明了要請求的物件是被放在 `www.someschool.edu` 這個主機上。你可能會覺得這行根本沒必要,因為我們不是早就跟那台主機建立連線了嗎。但我們在 2.2.5 節會提到,這個欄位是給網頁代理器的快取 (Web proxy caches) 看的 * `Connection: close`: 這行是用來告訴伺服器在回覆這個請求之後就把連線關閉 * `User-agent: Mozilla/5.0`: 這行用來說明是哪一種 user agent,也就是是什麼種類的瀏覽器在送出請求的。在這個例子中,請求是由 Firefox 瀏覽器送出的。這個欄位之所以有用,是因為伺服器有可能會根據 user agent 的不同而送出同一個物件的不同版本(雖然版本不同但是網址是一樣的) * `Accept-language: fr`: 這行用來告訴伺服器這個使用者比較想要看到法語版本的檔案(如果有得挑的話),如果沒有法語版本,那伺服器就會傳預設語言的版本。`Accept-language` 只是 HTTP 提供的眾多內容協商 (content negotiation) 欄位的其中一個。 ![](https://hackmd.io/_uploads/Hkx079Ppn.png) > Figure 2.8 HTTP 請求訊息的通用格式(sp 為空白字元,cr 為回車字元,lf 為換行字元,[cr 和 lf 合起來是一個換行](https://zh.wikipedia.org/zh-tw/%E6%8F%9B%E8%A1%8C)) 例子看完了,現在我們來看看完整的請求訊息格式到底長怎樣。Figure 2.8 展示了 HTTP 請求訊息的格式,長得跟上面的例子蠻像的。你可能會注意到,在標頭行下面的換行之後,還有一個叫作「實體主體 (entity body)」的欄位。在請求方法是 `GET` 的時候,這個欄位會是空的,但是我們會在使用 `POST` 方法時用到這個欄位。HTTP 客戶端時常會在提交使用者表單時使用 `POST` 方法,例如:讓使用者提交一個搜尋關鍵字給搜尋引擎。使用 `POST` 方法時,使用者依然是在跟伺服器要一個網頁,但此時伺服器會根據使用者在表單裡填寫的需求回傳特定的內容給使用者。 說是這麼說,但其實要提交使用者在表單內填寫的內容,並不一定要使用 `POST` 方法。很多時候我們也會把使用者輸入的表單欄位放在 URL 當中,然後使用 `GET` 方法送出請求。例如:如果有一個使用 `GET` 方法的表單,當中有兩個欄位,使用者分別填入了 `monkeys` 和 `bananas`,那麼請求的 URL 就會長得像 `www.somesite.com/animalsearch?monkeys&bananas`。在我們每天用搜尋引擎進行搜尋的時候,應該都有看過長得像這樣的 URL。 `HEAD` 方法和 `GET` 方法很像,當伺服器收到一個 `HEAD` 方法的請求時,他會回應一個 HTTP 訊息,但是不會回傳網頁物件,這通常是開發者拿來除錯用的。`PUT` 方法通常會配合網站的發佈功能一起使用,它讓使用者可以往伺服器上一個特定的路徑上傳物件。`PUT` 方法也可以用在應用程式想要上傳一個物件給伺服器的時候。`DELETE` 方法讓使用者或是應用程式可以刪除伺服器上的一個物件。 ### HTTP Response Message 在這裡我們提供一個典型的 HTTP 回應訊息。這個回應訊息可以用來回應上面我們剛討論完的那個請求訊息。 ``` HTTP/1.1 200 OK Connection: close Date: Tue, 18 Aug 2015 15:44:04 GMT Server: Apache/2.2.3 (CentOS) Last-Modified: Tue, 18 Aug 2015 15:11:03 GMT Content-Length: 6821 Content-Type: text/html (data data data data data ...) ``` 注意觀察這段回應訊息,你會發現他分成三個部份:第一行是**狀態行 (status line)**,接著有六行的**標頭行**,最後是**實體主體**。實體主體是客戶端真正感興趣的部份,他包含了請求的物件本身(那個 data data ... 的部份)。狀態行包含了三個欄位:版本號欄位、狀態碼,還有對應的狀態訊息。在這個例子中,伺服器使用的 HTTP 版本號是 HTTP/1.1 ,狀態是「一切都很好 (OK)」(有找到伺服器、伺服器有成功送出請求的物件)。 接著再來看看標頭行: * `Connection: close`: 這行用來告訴客戶端伺服器會在送完回應後就把 TCP 連線關掉。 * `Date: Tue, 18 Aug 2015 15:44:04 GMT`: 這行代表這段回應訊息被產生出來的時間戳記。注意這並不是請求的物件被創造出來或是最後一次被修改的時間,而是伺服器在硬碟裡找到了這個資料、把他放進訊息中,然後送出這段訊息的時間。 * `Server: Apache/2.2.3 (CentOS)`: 這行代表回應的伺服器是一個 Apache 網頁伺服器,跟請求訊息中的 `user-agent` 欄位相對應。 * `Last-Modified: Tue, 18 Aug 2015 15:11:03 GMT`: 這行代表所請求的物件被創造出來或是最後一次被修改的時間。這個欄位對於物件快取來說非常重要,無論是在客戶本地端的快取或是在網路中的快取伺服器(或叫作代理伺服器)都一樣。關於這些我們稍後就會詳細討論。 * `Content-Length: 6821`: 這行代表被送出的物件總共有 6821 個位元組那麼長。 * `Content-Type: text/html`: 這行代表送出的物件是一個 HTML 文字檔(通常物件的檔案類型必須以這個欄位為準,而不是看他的副檔名)。 ![](https://hackmd.io/_uploads/Hycx4jD6n.png) > Figure 2.9 HTTP 回應訊息的通用格式 例子看完了,現在我們來看看完整的回應訊息格式到底長怎樣。Figure 2.9 展示了 HTTP 回應訊息的格式,整體來說跟請求訊息的格式是一樣的。在這邊我們要再針對狀態碼和他對應的狀態訊息多著墨一些。狀態碼和狀態訊息代表了這個請求的結果,一些常見的狀態碼和他們對應的訊息有以下這些: * `200 OK`: 請求成功執行了,並且資訊也在回應中正常地回傳了 * `301 Moved Permanently`: 所請求的物件已經被永久的移動到另一個地方了,新的 URL 會寫在標頭行中的 `Location: ` 欄位中,客戶端會自動往這個新的 URL 做請求 * `400 Bad Request`: 這是當伺服器看不懂請求訊息時會用的一個通用的錯誤碼 * `404 Not Found`: 所請求的檔案不存在於這個伺服器上 * `505 HTTP Version Not Supported`: 請求所使用的 HTTP 協定版本該伺服器不支援 要怎麼看到一個真正的 HTTP 回應訊息呢?非常簡單,很推薦你試試看!首先用 Telnet 連進你敢興趣的網站,然後打一個一行的請求訊息,去要一個該伺服器上有的物件。例如,如果你有終端機可以用,你可以試試看打以下指令: ``` $ telnet gaia.cs.umass.edu 80 ... GET /kurose_ross/interactive/index.php HTTP/1.1 Host: gaia.cs.umass.edu ``` (打完之後記得按兩下 Enter)這個指令會開啟一個通往 `gaia.cs.umass.edu` 埠號 80 的 TCP 連線,然後送出一個 HTTP 請求訊息。你應該會看到回應訊息包含了一個 base HTML file,這是本書後面的互動式作業會用到的網頁(:0)。如果你只想要看到 HTTP 回應訊息本身,而不要這個網頁的話,就把請求方法 `GET` 改成 `HEAD`。 ![](https://hackmd.io/_uploads/SyrmPowa3.png) > (真的可以耶 XD) 在這個小節中我們討論了幾個 HTTP 請求和回應訊息會用到的標頭欄位。在 HTTP 的規格書中,這樣的標頭欄位有一卡車,可以是由瀏覽器、伺服器,或是網頁快取伺服器插入的,這裡我們只講到了當中的一點點而已,在底下我們也只會再多講一兩個。我們非常推薦你去看這篇很好懂又很詳盡的,有關 HTTP 協定以及其標頭欄位和狀態欄位的討論文章 [Krishnamurthy 2001]。 瀏覽器和伺服器,分別是怎麼決定要在請求訊息和回應訊息中插入哪些標頭欄位的呢?瀏覽器會根據自己的瀏覽器類型和版本(例如:實作 HTTP/1.0 標準的瀏覽器就不可能會生出 HTTP/1.1 標準中的欄位)、使用者的設定(例如:偏好的語言),還有瀏覽器現在是否擁有被快取起來,但可能已經過期的檔案...等等這些訊息,來決定要插入哪些標頭欄位到請求訊息中。伺服器也是類似的道理,伺服器有不同的廠牌、版本,還有設定,這些都會影響到回覆訊息中的標頭欄位。 ## 2.2.4 User-Server Interaction: Cookies 先前我們有提到,HTTP 伺服器是無狀態的。這簡化了伺服器的設計,讓工程師可以開發出高效能的伺服器,以應付成千上萬的 TCP 連線。然而,很多時候我們都會希望網站能夠辨識出不同的使用者,這可能是為了要限制使用者存取某些資訊,或是提供客製化的網頁內容。為了達到這個目的,HTTP 使用了 cookies。 Cookies 被定義在 [RFC 6265] 當中,他讓網站可以記住每一個使用者。現今幾乎每一個主要的電子商務網站都會使用 cookies。 ![](https://hackmd.io/_uploads/r1pux3Pan.png) > Figure 2.10 利用 cookies 機制保留使用者狀態 如同 Figure 2.10 所示, cookies 機制包含了四個部份: 1. HTTP 回應訊息中的 cookies 標頭欄位 2. HTTP 請求訊息中的 cookies 標頭欄位 3. 在使用者的終端系統中,由瀏覽器所管理的 cookies 檔案 4. 在伺服器端的後端資料庫 我們使用 Figure 2.10 來說明 cookies 是怎麼運作的: * 假設有一個使用者某甲,他總是從自己家裡的電腦用 IE 來上網。有一天,他有史以來第一次進入了 Amazon 的網站。我們假設他之前已經有去過 eBay 的網站好了。 * 當這個請求進到 Amazon 的網頁伺服器後,該伺服器就會產生出一個識別用的號碼,然後在後端資料庫裏面用這個識別碼當作索引創出一筆資料。 * 接著,伺服器會送出回應到某甲的電腦裡,這個回應會有一條 `Set-cookie:` 的標頭欄位,欄位值就是剛剛那個識別碼,例如: ``` Set-cookie: 1678 ``` * 當某甲的瀏覽器收到這個回應之後,他會看到有 `Set-cookie:` 這個欄位,接著瀏覽器就會往他所管理的一個特別的 cookies 檔案裏面寫入一條資料,這條資料會包含 Amazon 網站的主機名稱還有剛剛傳過來的這個識別碼。因為某甲之前已經逛過 eBay 了,所以這個檔案裏面已經存在有 eBay 那邊的 cookies 資料。 * 某甲繼續在 Amazon 的網站上到處逛,每當他請求一次 Amazon 的網頁,他的瀏覽器就會往 cookies 檔案裡看看,找到他在這個網站上的識別碼,然後在請求的標頭欄位中夾帶上這個識別碼,也就是會在請求中加上這一行: ``` Cookie: 1678 ``` 如此一來,Amazon 的伺服器就可以追蹤某甲在他們網站上的活動。Amazon 可能不會知道某甲叫什麼名字,但是他們會很清楚地知道使用者 1678 剛剛逛過了哪些頁面、逛這些頁面的順序,還有什麼時候逛的!Amazon 可以藉由 cookies 來提供購物車的服務,也就是紀錄下某甲想買的所有東西,讓某甲可以在逛完之後做一次付款 * 假設某甲在一個禮拜之後又回來逛 Amazon,他的瀏覽器會繼續在請求中加上 `Cookie: 1678` 這一行。Amazon 就可以根據某甲之前逛過的頁面來推薦商品給他。假設某甲有在 Amazon 註冊,提供了他的本名、email、地址,還有信用卡號碼的話,Amazon 就可以把這些資訊紀錄到資料庫裏面,並且把某甲的資訊跟 1678 這個辨識碼關聯起來。這就是 Amazon 和其他的電商網站提供「一鍵購物」的方式 —— 當某甲多次在他們網站上消費的時候,他可以不需要一直重打自己的名字和信用卡等資訊。 藉由這個例子,我們現在可以知道 cookies 是怎麼用來辨識使用者的身份的了。在使用者第一次造訪某網站時,他可以提供自己的資訊(像是名字之類的)。在接下來的對話 (session) 中,瀏覽器都會把 cookies 丟給伺服器,伺服器因此就可以認出該使用者。藉由使用 cookies,我們就能在無狀態的 HTTP 上創造出使用者對話層 (user session layer)。例如:當使用者登入了一個 email 網站時,瀏覽器會附上 cookies 給伺服器,讓伺服器可以在接下來的整個對話過程中都認得這個使用者。 雖然 cookies 簡化了使用者使用電商網站的流程,但這樣的機制其實也極具爭議,時常被視為侵犯了使用者的隱私權。就像我們剛剛看到的,藉由結合 cookies 和使用者提供的帳號資訊,電商其實可以掌握不少使用者的行為,並且有可能會偷偷把這些資訊賣給第三方。Cookie Central [Cookie Central 2016] 上有關於 cookie 機制爭議的更多資訊。 ## 2.2.5 Web Caching ![](https://hackmd.io/_uploads/rke9RKOTh.png) > Figure 2.11 客戶端透過網頁快取來請求物件 **Web 快取 (Web cache)**,或是叫作**代理伺服器 (proxy server)**,是一個可以代替原本提供網頁的伺服器去滿足使用者的 HTTP 請求的一種網路實體。Web 快取有自己的儲存空間(硬碟之類的),他會把最近有人存取過的網頁物件的副本存放在自己的儲存空間裡。如同 Figure 2.11 所示,使用者可以把自己的瀏覽器設定成把所有的 HTTP 請求都優先送往 Web 快取去拿東西。例如:假設有一個瀏覽器想要請求 ` http://www.someschool.edu/campus.gif` 這個物件,那麼會發生以下流程: 1. 瀏覽器會建立一個通往 Web 快取的 HTTP 請求並跟 Web 快取請求物件。 2. Web 快取會檢查看看自己有沒有該物件的副本,如果有,他就直接把該物件回應給請求的瀏覽器了。 3. 如果 Web 快取沒有該物件,那他就會開啟一個通往原本要請求的伺服器(`www.someschool.edu`)的 TCP 連線,然後跟那台伺服器請求物件。伺服器收到來自 Web 快取的請求之後,他就會回應該物件給 Web 快取。 4. 當 Web 快取收到了該物件,他會存一份副本在自己的儲存空間中,然後把副本回應回去給一開始請求的瀏覽器(透過原本那個在瀏覽器和 Web 快取之間的 TCP 連線)。 我們會注意到 Web 快取既是一個客戶端,也是一個伺服器。當他從瀏覽器收到請求並回應時,他是一個伺服器;當他往原本的伺服器請求物件並收到回應時,他是一個客戶端。 一般來說, Web 快取會是由 ISP 購入並安裝的。例如:許多大學都會安裝 Web 快取在他們的校園網路當中,然後把所有校園內的瀏覽器都設定成會優先往這些 Web 快取送請求。另一個例子:大型的家用網路供應商(像是 Comcast)可能會在他們的網路範圍內安裝一到多個快取,然後把使用他們接取網路的瀏覽器設定成會優先存取這些快取。 Web 快取之所以被引入網際網路中,主要是基於以下兩個理由。第一, Web 快取通常可以大幅降低客戶端請求物件的所需時間,尤其是在客戶端和目標伺服器之間的瓶頸頻寬遠低於客戶端和快取之間的瓶頸頻寬時。如果客戶端和快取之間有一個高速的網路通道(通常都有),而且快取也同時擁有客戶端想要的物件的話,那快取就可以非常快速地把物件傳遞給客戶端。第二,Web 快取通常也可以大幅減少大型機構(例如學校或是公司)的接取網路到網際網路核心之間的流量。若可以減少這些在接取網路的流量,大型機構就不需要一直擴增他們的頻寬,這可以省下不少開銷。更好的是,Web 快取還能大幅減少 Web 服務在整個網際網路中的流量,因此所有的應用程式的效能都會變得更好。 ![](https://hackmd.io/_uploads/BywjojO62.png) > Figure 2.12 在機構網路和網際網路之間的瓶頸 為了更深入了解快取所帶來的效益,我們可以來看看 Figure 2.12 中所展示的例子。該圖例中有兩個網路 —— 大型機構自己的網路還有公用的網際網路。機構網路是一個高速的區域網路,機構網路內的路由器和網際網路內的路由器藉由一個 15 Mbps 的線路連接在一起。網頁伺服器在網際網路上,遍佈全世界。假設平均的網頁物件大小是 1 Mbits,而從機構網路中的瀏覽器送出的 HTTP 請求速度平均來說是每秒 15 個請求。並假設 HTTP 請求訊息本身的大小可以忽略,因此不會在網路中造成流量。再假設在網際網路中那一台和接取網路鄰接的路由器,從轉發出一個 HTTP 請求(通常裝在一個 IP 資料塊裡),到收到該請求的回應(通常裝在多個 IP 資料塊中),平均需要花 2 秒,我們通常把這個時間差叫作「網際網路延遲 (Internet delay)」。 總回應時間,也就是從瀏覽器送出請求到他真的收到請求的物件這中間所需的時間,會是區域網路延遲、接取網路延遲(也就是上圖中兩個路由器之間的傳輸時間),以及網際網路延遲三個時間差的加總。現在我們非常粗略的來估算一下總延遲是多少。在區域網路中的流量強度是(請參考 [1.4.2](https://hackmd.io/y8IeQ2X7Txa15ZSo8qrBsA#142-Queuing-Delay-and-Packet-Loss) 的算式) $$ (15 \text{ requests/sec}) \cdot (1 \text{ Mbits/request}) / (100 \text{ Mbps}) = 0.15 $$ 但是在接取線路(從網際網路的路由器到機構的路由器)上的流量強度會是 $$ (15 \text{ requests/sec}) \cdot (1 \text{ Mbits/request}) / (15 \text{ Mbps}) = 1 $$ 當區域網路中的流量強度是 0.15 時,一般來說延遲大概會是十幾毫秒,也就是說,我們幾乎可以忽略在區域網路中的延遲。但是,就像我們在 1.4.2 節中所討論的,當流量強度愈接近 1,這段線路所造成的延遲就會變得非常大,而且會毫無上限地增長。因此,平均的回應時間就會上升至好幾分鐘的等級,使用者是不可能會接受的。很明顯,我們必須做點什麼來處理這個問題。 一個有效的解法是把接取線路的頻寬從 15 Mbps 擴增到 100 Mbps,這麼一來接取線路上的流量強度就會下降到 0.15,延遲就會變成跟區域網路延遲一樣差不多可以忽略的等級了。如此一來,總回應時間大約會是 2 秒,大約就是網際網路延遲的時間。但要這麼做,機構就必須要把他們的接取網路從 15 Mbps 升級到 100 Mbps,這會是一條不小的開銷。 ![](https://hackmd.io/_uploads/S1255a_62.png) > Figure 2.13 在機構網路當中加一台快取 另一個解法是買一台 Web 快取來裝在機構的網路裏面,我們把這個解法描繪在 Figure 2.13 裏面。實務上一般來說,快取的命中率 (hit rate) ,也就是由快取所滿足的請求佔所有請求的百分比,大約會介於 0.2 到 0.7 之間。為了方面說明,我們假設在這個例子中命中率是 0.4 好了。由於客戶端和快取之間是由高速的區域網路線路所連接的,因此有大約 40% 的請求,都可以由快取在大約 10 毫秒以內立刻滿足,但還有另外 60% 左右的請求必須要通過接取線路送到網際網路,由原本的伺服器來滿足。儘管如此,因為通過接取網路的請求量已經下降到原本的 60%,因此流量強度也會從 1.0 降低到 0.6。一般來說,當流量強度低於 0.8 時延遲就會變得非常小,在 15 Mbps 的線路上大約是十幾毫秒的等級,跟 2 秒的網際網路延遲比起來已經是一個可以忽略的大小。基於以上假設,平均的延遲就會是 $$ 0.4 \cdot (0.01 \text{ seconds}) + 0.6 \cdot (2.01 \text{ seconds}) $$ 大約只會比 1.2 秒還要再多一點點而已。這第二個解法所造成的總回應時間比第一個解法還要短,還不需要升級網路線。雖然機構還是需要購買並安裝 Web 快取,但這個開銷是很低的 —— 許多的快取用的都是在公共領域中的軟體(不用付費買專有軟體的意思),而且可以跑在便宜的個人電腦上。 由於**內容傳遞網路 (Content Distribution Network, CDN)** 的盛行,Web 快取在現今的網際網路中的重要性與日俱增。CDN 公司會在網際網路上安裝許多在地理位置上分散各處的快取,如此一來大量的流量就會被本地化。CDN 有分為共享 CDN(像是 Akamai 和 Limelight)以及專用 CDN(像是 Google 和 Netflix 自己的 CDN)。我們會在 2.6 節更詳細的討論 CDN。 ### The Conditional GET 雖然從使用者的角度來看,快取機制確實可以大幅減少回應時間,但是這也會產生一個新的問題:存放在快取機器上的檔案有可能會過期。換句話說,當網頁物件被快取在本地端之後,在網頁伺服器上的版本是有可能會被修改的。還好,HTTP 有一個機制可以讓快取去確認存放在他那邊的檔案是不是最新版本。這個機制叫作**條件請求 (conditional GET)**。一個 HTTP 請求若有滿足以下兩個條件就會被稱作條件請求:(1) 該請求的方法是 GET 方法 (2) 該請求的標頭有一個 `If-Modified-Since: ` 欄位。 為了深入了解條件請求是怎麼進行的,我們來看一個例子。 1. 首先,代理伺服器會代替原本發出請求的瀏覽器,去往目標的網頁伺服器發送以下請求: ``` GET /fruit/kiwi.gif HTTP/1.1 Host: www.exotiquecuisine.com ``` 2. 再來,目標伺服器會把所請求的物件,用以下回應訊息回傳給快取: ``` HTTP/1.1 200 OK Date: Sat, 3 Oct 2015 15:39:29 Server: Apache/1.3.0 (Unix) Last-Modified: Wed, 9 Sep 2015 09:23:24 Content-Type: image/gif (data data data data data ...) ``` 快取會把物件回傳給請求的瀏覽器,但也會留一份副本在自己的電腦上。最重要的是,快取會把該檔案最後一次被修改的時間跟檔案記在一起。 3. 接著,一個禮拜之後,有另一個瀏覽器也跟這個快取要了同一個物件,而且該物件還存放在儲存空間中。因為已經過了一個禮拜,伺服器上的該檔案可能已經被修改過了,所以快取會送出一個條件請求,來進行版本確認。準確來說,快取會送出以下請求: ``` GET /fruit/kiwi.gif HTTP/1.1 Host: www.exotiquecuisine.com If-modified-since: Wed, 9 Sep 2015 09:23:24 ``` 注意到 `If-modified-since: ` 這個欄位的值跟快取在一個禮拜前收到的該檔案的 `Last-Modified: ` 的值是一模一樣的。這個條件請求是在告訴伺服器,只有在該檔案在這個時間之後有被修改,才需要送檔案過來。 4. 接著,Web 伺服器會回傳以下訊息給快取: ``` HTTP/1.1 304 Not Modified Date: Sat, 10 Oct 2015 15:39:29 Server: Apache/1.3.0 (Unix) (empty entity body) ``` 我們會看到,伺服器依然會針對條件請求回一個回應訊息給快取,但是不會附上所請求的物件。附上物件只會浪費頻寬並且增加使用者等待的時間,尤其是檔案很大時更是如此。值得注意的是,這個回應的狀態是 `304 Not Modified`,用來告訴快取說你可以直接把你那邊那份副本丟回去瀏覽器就好了。 HTTP 我們就討論到這邊了,這是我們第一個深入了解的網際網路協定(應用層協定)。我們學到了 HTTP 的訊息格式,以及客戶端和伺服器在收發這些訊息時會做什麼動作。我們也學到了一些跟 Web 應用基礎建設有關的知識,包含快取、cookies,還有後端資料庫,這些機制都用某種方式被包含在 HTTP 協定中。 ---- [<< 2.1 Principles of Network Applications](https://hackmd.io/@kaeteyaruyo/computer-networking-2-1) | [目錄](https://hackmd.io/@kaeteyaruyo/computer-networking-index) | [2.3 Electronic Mail in the Internet >>](https://hackmd.io/@kaeteyaruyo/computer-networking-2-3)