認識 HTTP 超文本傳輸協定(一):概述

前言

本文為〈HTTP - MDN Web docs〉的學習筆記。

簡介

超文本傳輸協定(HTTP)是一種用來傳輸超媒體文件(像是 HTML 文件)的應用層協定,被設計來讓瀏覽器和伺服器進行溝通,但也可做其他用途。HTTP 遵循標準 client-server 模式,由客戶端連線以發送請求,然後等待接收回應。HTTP 是一種無狀態協定,意思是伺服器不會保存任兩個請求間的任何資料(狀態)。儘管作為 TCP/IP 的應用層,HTTP 亦可應用於其他可靠的傳輸層(例如 UDP),只要不會無聲無息地遺失訊息即可。

概觀

HTTP 是一種協議,它允許資源,諸如 HTML 文件的抓取。它是 Web 上任何資料交換的基礎,並且是 client-server 協議,這意味著請求是由收件人(通常是 Web 瀏覽器)發起的。完整的文件是從獲取的不同子文件中重建的,例如文本、佈局描述、圖像、影片、腳本等。

客戶端和服務端通過交換各自的訊息(與資料流正好相反)進行交互。由像瀏覽器這樣的客戶端發出的訊息叫做 requests,被服務端響應的訊息叫做 responses。

HTTP 被設計於 20 世紀 90 年代初期,是一種可擴展的協議。它是應用層的協議,通過 TCP,或者是 TLS——加密的 TCP 連接來發送,理論上任何可靠的傳輸協議都可以使用。因為其良好的擴展性,時至今日,它不僅被用來傳輸超文本文件,還用來傳輸圖片、影片或者向伺服器發送如 HTML 表單這樣的訊息。HTTP 還可以根據網頁需求,僅獲取部分 Web 文件內容更新網頁。

基於 HTTP 的元件系統

HTTP 是一個 client-server 協議:請求通過一個實體被發出,實體也就是用戶代理。大多數情況下,這個用戶代理都是指瀏覽器,當然它也可能是任何東西,比如一個爬取網頁生成維護搜尋引擎索引的機器爬蟲。

每一個發送到伺服器的請求,都會被伺服器處理並返回一個訊息,也就是 response。在這個請求與響應之間,還有許許多多的被稱為 proxies 的實體,他們的作用與表現各不相同,比如有些是閘道器,還有些是快取等。

客戶端

所謂 user-agent 就是任何能夠為用戶發起行為的工具。這個角色通常都是由瀏覽器來扮演。一些例外情況,比如說工程師使用的程式,以及 Web 開發人員的測試應用程式。

瀏覽器總是作為發起一個請求的實體,它永遠不是伺服器。

要展現一個網頁,瀏覽器首先發送一個請求來獲取頁面的 HTML 文件,再解析文件中的資源訊息發送其他請求,獲取可執行腳本或 CSS 樣式來進行頁面佈局渲染,以及一些其它頁面資源(如圖片和影片等)。然後,瀏覽器將這些資源整合到一起,展現出一個完整的文件,也就是網頁。瀏覽器執行的腳本可以在之後的階段獲取更多資源,並相應地更新網頁。

一個網頁就是一個超文本文件。也就是說,有一部分顯示的文本可能是連結,啟動它(通常是鼠標的點擊)就可以獲取一個新的網頁,使得用戶可以控制客戶端盡情瀏覽。瀏覽器來負責發送 HTTP 請求,並進一步解析 HTTP 返回的訊息,以向用戶提供明確的響應。

服務端

在上述通訊過程的另一端,是由 Web Server 來服務並提供客戶端所請求的文件。Server 只是虛擬意義上代表一個機器:它可以是負載平衡的一組伺服器組成的計算機叢集,也可以是一種複雜的軟體,通過向其他計算機(如快取、資料庫伺服器、電子商務伺服器等)發起請求來獲取部分或全部的資源。

Server 不一定是一台機器,但一個機器上可以裝載眾多的伺服器。在 HTTP/1.1 和 Host 頭部中,它們甚至可以共享同一個 IP 位址。

代理

在瀏覽器和伺服器之間,有許多計算機和其他設備轉發了 HTTP 訊息。由於 Web 層次結構的原因,它們大多都出現在傳輸層、網路層和物理層上,對於 HTTP 應用層而言就是透明的,雖然它們可能會對應用層的性能有重要影響。還有一部分是表現在應用層上的,被稱為代理(Proxies)。代理既可以表現得透明,又可以不透明(「改變請求」會通過它們)。代理主要有以下幾種作用:

  • 快取(可以是公開的,也可以是私有的,像瀏覽器的快取)
  • 過濾(像反病毒掃描、家長控制)
  • 負載平衡(讓多個伺服器服務不同的請求)
  • 認證(對不同資源進​​行權限管理)
  • 日誌記錄(允許存儲歷史訊息)

HTTP 的基本性質

HTTP 是簡單的

雖然下一代 HTTP/2 協議將 HTTP 訊息封裝到了幀(frames)中,HTTP 大體上還是被設計得簡單易讀。HTTP 報文能夠被人讀懂,還允許簡單測試,降低了門檻。

HTTP 是可擴展的

在 HTTP/1.0 中出現的 HTTP headers 讓協議擴展變得非常容易。只要服務端和客戶端就新 headers 達成語義一致,新功能就可以被輕鬆加入進來。

HTTP 是無狀態、有會話的

HTTP 是無狀態的:在同一個連接中,兩個執行成功的請求之間是沒有關係的。這就帶來了一個問題,用戶沒有辦法在同一個網站中進行連續的交互,比如在一個電商網站裡,用戶把某個商品加入到購物車,切換一個頁面後再次添加了商品,這兩次添加商品的請求之間沒有關聯,瀏覽器無法知道用戶最終選擇了哪些商品。而使用 HTTP 的頭部擴展,HTTP Cookies 就可以解決這個問題。把 Cookies 添加到頭部中,創建一個會話讓每次請求都能共享相同的上下文訊息,達成相同的狀態。

注意,HTTP 本質是無狀態的,但是使用 Cookies 可以創建有狀態的會話。

HTTP 和連接

一個連接是由傳輸層來控制的,這從根本上不屬於 HTTP 的範疇。HTTP 並不需要其底層的傳輸層協議是連接導向的,只需要它是可靠的,或不丟失訊息的(至少返回錯誤)。在網際網路中,有兩個最常用的傳輸層協議:TCP 是可靠的,而 UDP 不是。因此,HTTP 依賴於連接導向的 TCP 進行訊息傳遞,但連接並不是必須的。

在客戶端(通常指瀏覽器)與伺服器能夠交互(客戶端發起請求,伺服器返回響應)之前,必須在這兩者間建立一個 TCP 連接,打開一個 TCP 連接需要多次往返交換訊息(因此耗時)。HTTP/1.0 默認為每一對 HTTP 請求/響應都打開一個單獨的 TCP 連接。當需要連續發起多個請求時,這種模式比多個請求共享同一個 TCP 連接更低效。

為了減輕這些缺陷,HTTP/1.1 引入了流水線(被證明難以實現)和持久連接的概念:底層的 TCP 連接可以通過 Connection 頭部來被部分控制。HTTP/2 則發展得更遠,通過在一個連接複用訊息的方式來讓這個連接始終保持為「暖連接」。

設計一種更好的傳輸協議的進程一直在進行,Google 就研發了一種以 UDP 為基礎,能提供更可靠、更高效的傳輸協議 QUIC。

HTTP 控制項

多年以來,HTTP 良好的擴展性使得越來越多的 Web 功能歸其控制。快取和認證很早就可以由 HTTP 來控制了。另一方面,對同源同域的限製到 2010 年才有所改變。

以下是可以被 HTTP 控制的常見特性:

  • 快取:服務端能告訴代理和客戶端有哪些文件需要被快取、快取多久,而客戶端也能夠命令中間的快取代理來忽略存儲的文件。

  • 開放同源限制:為了防止網路窺聽和其它隱私洩漏,瀏覽器強制對網站做了分割限制。只有來自於相同來源的網頁才能夠獲取網站的全部訊息。這樣的限制有時反而成了負擔,HTTP 可以通過修改頭部來開放這樣的限制,因此 Web 文件是可以由不同域下的訊息拼接而成的(然而某些情況下,這樣做還是有安全因素考量的)。

  • 認證:一些頁面能夠被保護起來,僅讓特定的用戶進行訪問。基本的認證功能可以直接通過 HTTP 提供,使用 Authenticate 相似的頭部即可,或用 HTTP Cookies 來設置指定的會話。

  • 代理和通道:通常情況下,服務器或客戶端是處於內網的,對外網隱藏真實 IP 位址。因此 HTTP 請求就要通過代理越過這個網路屏障。但並非所有的代理都是 HTTP 代理。例如,Socks 協議的代理就運作在更底層,一些像 FTP 這樣的協議也能夠被它們處理。

  • 會話:使用 HTTP Cookies 允許你用一個服務端的狀態發起請求,這就創建了會話,雖然基本的 HTTP 是無狀態協議的。而這很有用,不僅是因為這能應用到像購物車這樣的電商業務上,更是因為這使得任何網站都能輕鬆為用戶定制展示內容了。

HTTP 流程

當客戶端想要和服務端進行訊息交互時(服務端是指最終服務器,或者是一個中間代理),過程表現為:

  1. 打開一個 TCP 連接:TCP 連接被用來發送一條或多條請求,以及接受響應訊息。客戶端可能打開一條新的連接,或重用一個已經存在的連接,或者也可能開幾個新的 TCP 連接連向服務端。

  2. 發送一個 HTTP 報文:HTTP 報文(在 HTTP/2 之前)是語義可讀的。在 HTTP/2 中,這些簡單的訊息被封裝在了幀裡頭,這使得報文不能被直接讀取,但是原理仍是相同的。

1
2
3
GET / HTTP/1.1
Host: developer.mozilla.org
Accept-Language: fr
  1. 讀取服務端返回的報文訊息:
1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
Date: Sat, 09 Oct 2010 14:28:02 GMT
Server: Apache
Last-Modified: Tue, 01 Dec 2009 20:18:22 GMT
ETag: "51142bc1-7449-479b075b2891b"
Accept-Ranges: bytes
Content-Length: 29769
Content-Type: text/html

<!DOCTYPE html... (here comes the 29769 bytes of the requested web page)
  1. 關閉連接或者為後續請求重用連接。

當 HTTP 流水線啟動時,後續請求都可以不用等待第一個請求的成功響應就被發送。然而 HTTP 流水線已被證明很難在現有的網路中實現,因為現有網路中有很多老舊的軟體與現代版本的軟體共存。因此,HTTP 流水線已被在有多請求下表現得更穩健的 HTTP/2 的幀所取代。

HTTP 報文

HTTP/1.1 以及更早的 HTTP 協議報文都是語義可讀的。在 HTTP/2 中,這些報文被嵌入到了一個新的二進制結構——幀。幀允許實現很多優化,比如報文頭部的壓縮和復用。即使只有原始 HTTP 報文的一部分以 HTTP/2 發送出來,每條報文的語義依舊不變,客戶端會重組原始 HTTP/1.1 請求。因此用 HTTP/1.1 格式來理解 HTTP/2 報文仍舊有效。

有兩種 HTTP 報文的類型,分別是請求與響應,每種都有其特定的格式。

請求

HTTP 請求的一個例子:

1
2
3
GET / HTTP/1.1
Host: developer.mozilla.org
Accept-Language: fr

請求由以下元素組成:

  • 一個 HTTP 的 method,經常是由一個動詞像 GET、POST、OPTIONS、HEAD 等定義客戶端的動作行為。通常客戶端的操作都是獲取資源(GET 方法)或者發送 HTML 表單值(POST 方法),雖然在一些情況下也會有其他操作。

  • 要獲取的資源的路徑,通常是上下文中就很明顯的元素資源的 URL,它沒有 protocol(如 http:// )、domain(如 developer.mozilla.org),或是 TCP 的埠號(HTTP 一般在 80 端口)。

  • HTTP 協議版本號,為服務端表達其他訊息的可選頭部。對於一些像 POST 這樣的方法,報文的 body 就包含了發送的資源,這與響應報文的 body 類似。

響應

HTTP 響應的一個例子:

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
Date: Sat, 09 Oct 2010 14:28:02 GMT
Server: Apache
Last-Modified: Tue, 01 Dec 2009 20:18:22 GMT
ETag: "51142bc1-7449-479b075b2891b"
Accept-Ranges: bytes
Content-Length: 29769
Content-Type: text/html

<!DOCTYPE html... (here comes the 29769 bytes of the requested web page)

響應報文包含了下面的元素:

  • HTTP 協議版本號。
  • 一個狀態碼(status code),來告知對應請求執行成功或失敗,以及失敗的原因。
  • 一個狀態訊息,這個訊息是非權威的狀態碼描述訊息,可以由服務端自行設定。
  • HTTP headers,與請求頭部類似。
  • 可選項,比起請求報文,響應報文中更常見的是獲取的資源 body。

總結

HTTP 是一種簡單可擴展的協議,其 client-server 的結構以及輕鬆擴展頭部訊息的能力使得 HTTP 可以和 Web 共同發展。

即使 HTTP/2 為了提高性能將 HTTP 報文嵌入到幀中這一舉措增加了複雜度,但是從 Web 應用的角度來看,報文的基本結構沒有變化,從 HTTP/1.0 發布起就是這樣的結構。會話依舊簡單,通過一個簡單的 HTTP message monitor 就可以查看和糾錯。