HTTP 協定入門
HTTP 協定是網際網路的基礎協定,也是網頁開發的必備知識,最新版本 HTTP/2 更是讓它成為技術熱點。
本文介紹 HTTP 協定的曆史演變和設計思路。
一、HTTP/0.9
HTTP 是基于 TCP/IP 協定的應用層協定。它不涉及資料包(packet)傳輸,主要規定了用戶端和伺服器之間的通信格式,預設使用80端口。
最早版本是1991年釋出的0.9版。該版本極其簡單,隻有一個指令
GET
。
GET /index.html
上面指令表示,TCP 連接配接(connection)建立後,用戶端向伺服器請求(request)網頁
index.html
。
協定規定,伺服器隻能回應HTML格式的字元串,不能回應别的格式。
<html> <body>Hello World</body> </html>
伺服器發送完畢,就關閉TCP連接配接。
二、HTTP/1.0
2.1 簡介
1996年5月,HTTP/1.0 版本釋出,内容大大增加。
首先,任何格式的内容都可以發送。這使得網際網路不僅可以傳輸文字,還能傳輸圖像、視訊、二進制檔案(都是位元組流)。這為網際網路的大發展奠定了基礎。
其次,除了
GET
指令,還引入了
POST
指令和
HEAD
指令,豐富了浏覽器與伺服器的互動手段。
再次,HTTP請求和回應的格式也變了。除了資料部分,每次通信都必須包括頭資訊(HTTP header),用來描述一些中繼資料。
其他的新增功能還包括狀态碼(status code)、多字元集支援、多部分發送(multi-part type)、權限(authorization)、緩存(cache)、内容編碼(content encoding)等。
2.2 請求格式
下面是一個1.0版的HTTP請求的例子。
GET / HTTP/1.0 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) Accept: */*
可以看到,這個格式與0.9版有很大變化。
第一行是請求指令,必須在尾部添加協定版本(
HTTP/1.0
)。後面就是多行頭資訊,描述用戶端的情況。
2.3 回應格式
伺服器的回應如下。
HTTP/1.0 200 OK Content-Type: text/plain Content-Length: 137582 Expires: Thu, 05 Dec 1997 16:00:00 GMT Last-Modified: Wed, 5 August 1996 15:55:28 GMT Server: Apache 0.84 <html> <body>Hello World</body> </html>
回應的格式是"頭資訊 + 一個空行(
\r\n
) + 資料"。其中,第一行是"協定版本 + 狀态碼(status code) + 狀态描述"。
2.4 Content-Type 字段
關于字元的編碼,1.0版規定,頭資訊必須是 ASCII 碼,後面的資料可以是任何格式。是以,伺服器回應的時候,必須告訴用戶端,資料是什麼格式,這就是
Content-Type
字段的作用。
下面是一些常見的
Content-Type
字段的值。
- text/plain
- text/html
- text/css
- image/jpeg
- image/png
- image/svg+xml
- audio/mp4
- video/mp4
- application/javascript
- application/pdf
- application/zip
- application/atom+xml
這些資料類型總稱為
MIME type
,每個值包括一級類型和二級類型,之間用斜杠分隔。
除了預定義的類型,廠商也可以自定義類型。
application/vnd.debian.binary-package
上面的類型表明,發送的是Debian系統的二進制資料包。
MIME type
還可以在尾部使用分号,添加參數。
Content-Type: text/html; charset=utf-8
上面的類型表明,發送的是網頁,而且編碼是UTF-8。
用戶端請求的時候,可以使用
Accept
字段聲明自己可以接受哪些資料格式。
Accept: */*
上面代碼中,用戶端聲明自己可以接受任何格式的資料。
MIME type
不僅用在HTTP協定,還可以用在其他地方,比如HTML網頁。
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <!-- 等同于 --> <meta charset="utf-8" />
2.5 Content-Encoding 字段
由于發送的資料可以是任何格式,是以可以把資料壓縮後再發送。
Content-Encoding
字段說明資料的壓縮方法。
Content-Encoding: gzip Content-Encoding: compress Content-Encoding: deflate
用戶端在請求時,用
Accept-Encoding
字段說明自己可以接受哪些壓縮方法。
Accept-Encoding: gzip, deflate
2.6 缺點
HTTP/1.0 版的主要缺點是,每個TCP連接配接隻能發送一個請求。發送資料完畢,連接配接就關閉,如果還要請求其他資源,就必須再建立一個連接配接。
TCP連接配接的建立成本很高,因為需要用戶端和伺服器三次握手,并且開始時發送速率較慢(slow start)。是以,HTTP 1.0版本的性能比較差。随着網頁加載的外部資源越來越多,這個問題就愈發突出了。
為了解決這個問題,有些浏覽器在請求時,用了一個非标準的
Connection
字段。
Connection: keep-alive
這個字段要求伺服器不要關閉TCP連接配接,以便其他請求複用。伺服器同樣回應這個字段。
Connection: keep-alive
一個可以複用的TCP連接配接就建立了,直到用戶端或伺服器主動關閉連接配接。但是,這不是标準字段,不同實作的行為可能不一緻,是以不是根本的解決辦法。
三、HTTP/1.1
1997年1月,HTTP/1.1 版本釋出,隻比 1.0 版本晚了半年。它進一步完善了 HTTP 協定,一直用到了20年後的今天,直到現在還是最流行的版本。
3.1 持久連接配接
1.1 版的最大變化,就是引入了持久連接配接(persistent connection),即TCP連接配接預設不關閉,可以被多個請求複用,不用聲明
Connection: keep-alive
。
用戶端和伺服器發現對方一段時間沒有活動,就可以主動關閉連接配接。不過,規範的做法是,用戶端在最後一個請求時,發送
Connection: close
,明确要求伺服器關閉TCP連接配接。
Connection: close
目前,對于同一個域名,大多數浏覽器允許同時建立6個持久連接配接。
3.2 管道機制
1.1 版還引入了管道機制(pipelining),即在同一個TCP連接配接裡面,用戶端可以同時發送多個請求。這樣就進一步改進了HTTP協定的效率。
舉例來說,用戶端需要請求兩個資源。以前的做法是,在同一個TCP連接配接裡面,先發送A請求,然後等待伺服器做出回應,收到後再發出B請求。管道機制則是允許浏覽器同時發出A請求和B請求,但是伺服器還是按照順序,先回應A請求,完成後再回應B請求。
3.3 Content-Length 字段
一個TCP連接配接現在可以傳送多個回應,勢必就要有一種機制,區分資料包是屬于哪一個回應的。這就是
Content-length
字段的作用,聲明本次回應的資料長度。
Content-Length: 3495
上面代碼告訴浏覽器,本次回應的長度是3495個位元組,後面的位元組就屬于下一個回應了。
在1.0版中,
Content-Length
字段不是必需的,因為浏覽器發現伺服器關閉了TCP連接配接,就表明收到的資料包已經全了。
3.4 分塊傳輸編碼
使用
Content-Length
字段的前提條件是,伺服器發送回應之前,必須知道回應的資料長度。
對于一些很耗時的動态操作來說,這意味着,伺服器要等到所有操作完成,才能發送資料,顯然這樣的效率不高。更好的處理方法是,産生一塊資料,就發送一塊,采用"流模式"(stream)取代"緩存模式"(buffer)。
是以,1.1版規定可以不使用
Content-Length
字段,而使用"分塊傳輸編碼"(chunked transfer encoding)。隻要請求或回應的頭資訊有
Transfer-Encoding
字段,就表明回應将由數量未定的資料塊組成。
Transfer-Encoding: chunked
每個非空的資料塊之前,會有一個16進制的數值,表示這個塊的長度。最後是一個大小為0的塊,就表示本次回應的資料發送完了。下面是一個例子。
HTTP/1.1 200 OK Content-Type: text/plain Transfer-Encoding: chunked 25 This is the data in the first chunk 1C and this is the second one 3 con 8 sequence 0
3.5 其他功能
1.1版還新增了許多動詞方法:
PUT
、
PATCH
、
HEAD
、
OPTIONS
、
DELETE
。
另外,用戶端請求的頭資訊新增了
Host
字段,用來指定伺服器的域名。
Host: www.example.com
有了
Host
字段,就可以将請求發往同一台伺服器上的不同網站,為虛拟主機的興起打下了基礎。
3.6 缺點
雖然1.1版允許複用TCP連接配接,但是同一個TCP連接配接裡面,所有的資料通信是按次序進行的。伺服器隻有處理完一個回應,才會進行下一個回應。要是前面的回應特别慢,後面就會有許多請求排隊等着。這稱為"隊頭堵塞"(Head-of-line blocking)。
為了避免這個問題,隻有兩種方法:一是減少請求數,二是同時多開持久連接配接。這導緻了很多的網頁優化技巧,比如合并腳本和樣式表、将圖檔嵌入CSS代碼、域名分片(domain sharding)等等。如果HTTP協定設計得更好一些,這些額外的工作是可以避免的。
四、SPDY 協定
2009年,谷歌公開了自行研發的 SPDY 協定,主要解決 HTTP/1.1 效率不高的問題。
這個協定在Chrome浏覽器上證明可行以後,就被當作 HTTP/2 的基礎,主要特性都在 HTTP/2 之中得到繼承。
五、HTTP/2
2015年,HTTP/2 釋出。它不叫 HTTP/2.0,是因為标準委員會不打算再釋出子版本了,下一個新版本将是 HTTP/3。
5.1 二進制協定
HTTP/1.1 版的頭資訊肯定是文本(ASCII編碼),資料體可以是文本,也可以是二進制。HTTP/2 則是一個徹底的二進制協定,頭資訊和資料體都是二進制,并且統稱為"幀"(frame):頭資訊幀和資料幀。
二進制協定的一個好處是,可以定義額外的幀。HTTP/2 定義了近十種幀,為将來的進階應用打好了基礎。如果使用文本實作這種功能,解析資料将會變得非常麻煩,二進制解析則友善得多。
5.2 多工
HTTP/2 複用TCP連接配接,在一個連接配接裡,用戶端和浏覽器都可以同時發送多個請求或回應,而且不用按照順序一一對應,這樣就避免了"隊頭堵塞"。
舉例來說,在一個TCP連接配接裡面,伺服器同時收到了A請求和B請求,于是先回應A請求,結果發現處理過程非常耗時,于是就發送A請求已經處理好的部分, 接着回應B請求,完成後,再發送A請求剩下的部分。
這樣雙向的、實時的通信,就叫做多工(Multiplexing)。
5.3 資料流
因為 HTTP/2 的資料包是不按順序發送的,同一個連接配接裡面連續的資料包,可能屬于不同的回應。是以,必須要對資料包做标記,指出它屬于哪個回應。
HTTP/2 将每個請求或回應的所有資料包,稱為一個資料流(stream)。每個資料流都有一個獨一無二的編号。資料包發送的時候,都必須标記資料流ID,用來區分它屬于哪個資料流。另外還規定,用戶端發出的資料流,ID一律為奇數,伺服器發出的,ID為偶數。
資料流發送到一半的時候,用戶端和伺服器都可以發送信号(
RST_STREAM
幀),取消這個資料流。1.1版取消資料流的唯一方法,就是關閉TCP連接配接。這就是說,HTTP/2 可以取消某一次請求,同時保證TCP連接配接還打開着,可以被其他請求使用。
用戶端還可以指定資料流的優先級。優先級越高,伺服器就會越早回應。
5.4 頭資訊壓縮
HTTP 協定不帶有狀态,每次請求都必須附上所有資訊。是以,請求的很多字段都是重複的,比如
Cookie
和
User Agent
,一模一樣的内容,每次請求都必須附帶,這會浪費很多帶寬,也影響速度。
HTTP/2 對這一點做了優化,引入了頭資訊壓縮機制(header compression)。一方面,頭資訊使用
gzip
或
compress
壓縮後再發送;另一方面,用戶端和伺服器同時維護一張頭資訊表,所有字段都會存入這個表,生成一個索引号,以後就不發送同樣字段了,隻發送索引号,這樣就提高速度了。
5.5 伺服器推送
HTTP/2 允許伺服器未經請求,主動向用戶端發送資源,這叫做伺服器推送(server push)。
常見場景是用戶端請求一個網頁,這個網頁裡面包含很多靜态資源。正常情況下,用戶端必須收到網頁後,解析HTML源碼,發現有靜态資源,再發出靜态資源請求。其實,伺服器可以預期到用戶端請求網頁後,很可能會再請求靜态資源,是以就主動把這些靜态資源随着網頁一起發給用戶端了。
六、參考連結
- Journey to HTTP/2, by Kamran Ahmed
- HTTP, by Wikipedia
- HTTP/1.0 Specification
- HTTP/2 Specification