SPDY網(wǎng)絡(luò)協(xié)議中的請(qǐng)求和響應(yīng)頭
SPDY 幀層運(yùn)行在可靠的傳輸層(如 TCP)之上,提供了多路復(fù)用、優(yōu)先級(jí)、頭部壓縮和服務(wù)端推送等 HTTP 不具備的功能。SPDY 連接都是持久的,連接建立后,客戶端和服務(wù)端會(huì)交換幀信息(framed messages)。SPDY 有兩種類型的幀:控制幀和數(shù)據(jù)幀。
SPDY 定義了多種控制幀,其中有三種用來管理流(stream):
SYN_STREAM:打開流;
SYN_REPLY:遠(yuǎn)程確認(rèn)新打開的流;
RST_STREAM:關(guān)閉流;
SYN_STREAM 和 SYN_REPLY
SYN_STREAM 控制幀用來打開流,它的格式如下:
BASH+------------------------------------+ |1| version | 1 | +------------------------------------+ | Flags (8) | Length (24 bits) | +------------------------------------+ |X| Stream-ID (31bits) | +------------------------------------+ |X| Associated-To-Stream-ID (31bits) | +------------------------------------+ | Pri|Unused | Slot | | +-------------------+ | | Number of Name/Value pairs (int32) | <+ +------------------------------------+ | | Length of name (int32) | | This section is the +------------------------------------+ | "Name/Value Header Block", | Name (string) | | and is compressed. +------------------------------------+ | | Length of value (int32) | | +------------------------------------+ | | Value (string) | | +------------------------------------+ | | (repeats) | <+
簡單介紹下這些字段含義:
***行是:控制位(數(shù)據(jù)幀的控制位是 0,控制幀是 1)、SPDY 版本和類型(SYN_STREAM 的類型是 1);
flags 是幀標(biāo)識(shí),有 0x01(FLAG_FIN)和 0x02(FLAG_UNIDIRECTIONAL)兩種。FIN 表示該幀是當(dāng)前流的***一幀,發(fā)送者隨后進(jìn)入半關(guān)閉狀態(tài);UNIDIRECTIONAL 作用是讓接收者進(jìn)入半關(guān)閉狀態(tài);
Length(長度),表示這一幀剩余部分字節(jié)數(shù)。對(duì)于 SYN_STREAM 來說,它是固定 10 字節(jié)加上壓縮后鍵 / 值對(duì)的長度;
Stream-ID 是流的標(biāo)識(shí)符,會(huì)被用于這個(gè)流里所有的幀??蛻舳顺跏蓟牧?id 必須是奇數(shù),服務(wù)端創(chuàng)建的流是偶數(shù),流 id 在兩端必須連續(xù);
Associated-To-Stream-ID,關(guān)聯(lián)的流。如果沒有關(guān)聯(lián)的流,它應(yīng)該為 0;
Pri(Priority),流優(yōu)先級(jí),0 表示優(yōu)先級(jí)***,7 表示***。發(fā)送者和接收者應(yīng)該盡可能的按照這個(gè)優(yōu)先級(jí)去處理流;
Name/Value Header Block(鍵 / 值頭部塊),SYN_STREAM 攜帶的一組鍵 / 值對(duì),這個(gè)塊一定會(huì)使用 zlib 壓縮;
SYN_REPLY 控制幀用來確認(rèn)新打開的流,它的格式是:
BASH+------------------------------------+ |1| version | 2 | +------------------------------------+ | Flags (8) | Length (24 bits) | +------------------------------------+ |X| Stream-ID (31bits) | +------------------------------------+ | Number of Name/Value pairs (int32) | <+ +------------------------------------+ | | Length of name (int32) | | This section is the +------------------------------------+ | "Name/Value Header Block", | Name (string) | | and is compressed. +------------------------------------+ | | Length of value (int32) | | +------------------------------------+ | | Value (string) | | +------------------------------------+ | | (repeats) | <+
這些字段與 SYN_STREAM 含義幾乎一樣:
***行是也是控制位、SPDY 版本和類型(SYN_REPLY 的類型是 2);
Length(長度),表示這一幀剩余部分字節(jié)數(shù)。對(duì)于 SYN_REPLY 來說,它是固定 4 字節(jié)加上壓縮后鍵 / 值對(duì)的長度;
RST_STREAM 和其他控制幀,以及數(shù)據(jù)幀與本文關(guān)系不大,這里略過。
SPDY 上的 HTTP 請(qǐng)求
客戶端通過 SYN_STREAM 幀來初始化請(qǐng)求。如果請(qǐng)求不包含正文部分(HTTP Body),那么必須設(shè)置 FLAG_FIN 標(biāo)志,表示客戶端不會(huì)在這個(gè)流上發(fā)送其他幀了;否則,客戶端會(huì)在 SYN_STREAM 之后發(fā)送一系列數(shù)據(jù)幀,并給***一個(gè)數(shù)據(jù)幀設(shè)置 FLAG_FIN。
SYN_STREAM 中的 Name/Value Header Block,幾乎與現(xiàn)在的 HTTP 頭部相同,但也有改變:
狀態(tài)行必須像其他 HTTP 頭部一樣展開為鍵 / 值對(duì)。我們知道,HTTP 協(xié)議請(qǐng)求中,***行有這些信息:
<method> <request-URL> <version>
在 SPDY 中,這些信息必須放在鍵 / 值對(duì)中:
:method,這個(gè)請(qǐng)求對(duì)應(yīng)的 HTTP method(如:GET、POST、HEAD 等);
:path,"/" 開頭的 url 路徑,參考 RFC3986;
:version,HTTP 版本號(hào)(如 HTTP/1.1);
另外,每個(gè)請(qǐng)求中,還需要補(bǔ)充以下兩個(gè)鍵 / 值對(duì):
:host,請(qǐng)求的主機(jī)和端口,參考 RFC1738,與當(dāng)前 HTTP 的 HOST 頭相同;
:scheme,URL 的協(xié)議部分(如 https);
所有頭部名都需要小寫。我們已經(jīng)看到,SPDY 新增的鍵 / 值對(duì)的 key 都是小寫的,其他已有的 HTTP 頭部的 key 也都需要轉(zhuǎn)成小寫。
不能發(fā)送某些頭部。Connection、Host、Keep-Alive、Proxy-Connection、Transfer-Encoding 這些頭都不能發(fā)送。這些頭多半與連接控制和傳輸方式有關(guān),SPDY 已經(jīng)不需要他們,HOST 則被 :host 代替。
客戶端必須支持 gzip 壓縮。也就是說,無論客戶端是否發(fā)送 accept-encoding,服務(wù)端始終可以發(fā)送 gzip 或者 deflate 編碼后的內(nèi)容。(擴(kuò)展閱讀:Nginx 在 SPDY 協(xié)議下不發(fā)送 Vary: Accept-Encoding 響應(yīng)頭)
如果服務(wù)端收到數(shù)據(jù)幀長度和不等于 content-length 的請(qǐng)求,必須返回 400(Bad Request)。同時(shí),對(duì)于 POST 請(qǐng)求,也需要包含 content-length 頭部。
另外,客戶端可以通過 SYN_STREAM 幀中的 Pri 字段,給不同資源指定不同的優(yōu)先級(jí)。后續(xù)我會(huì)專門寫文章介紹 Chrome 瀏覽器的優(yōu)先級(jí)策略。
如果 SYN_STREAM 幀沒有包含 :method、:host、:path、:scheme 以及 :version,服務(wù)端必須返回 400(Bad Request)。
SPDY 上的 HTTP 響應(yīng)
服務(wù)端用 SYN_REPLY 幀響應(yīng)客戶端的請(qǐng)求。同樣,F(xiàn)LAG_FIN 用來標(biāo)識(shí)該響應(yīng)是否包含正文。與 SPDY 請(qǐng)求類似,SPDY 響應(yīng)也有一些改變:
狀態(tài)行必須像其他 HTTP 頭部一樣展開為鍵 / 值對(duì)。我們知道,HTTP 協(xié)議響應(yīng)中,***行有這些信息:
<version> <status> <respon-phrase>
在 SPDY 中,他們也必須放在鍵/值對(duì)中:
:status,HTTP 響應(yīng)狀態(tài)碼(如:200 或 200 OK);
:version,響應(yīng)的 HTTP 版本號(hào)(如 HTTP/1.1);
所有頭部名都需要小寫。與前面請(qǐng)求頭規(guī)則一致。
不能發(fā)送某些頭部。Connection、Keep-Alive、Proxy-Connection、Transfer-Encoding 這些頭都不能發(fā)送。與請(qǐng)求頭類似。
響應(yīng)頭可以包含 content-length。如果 content-length 長度不等于響應(yīng)數(shù)據(jù)幀長度之和,客戶端必須忽略這個(gè)頭。
如果服務(wù)端的 SYN_REPLY 中不包含 :status 或 :version頭,客戶端必須回復(fù) RST_STREAM 幀。
#p#
SPDY 請(qǐng)求 / 響應(yīng)實(shí)例
通過 Chrome 開發(fā)工具的網(wǎng)絡(luò)面板,可以看到請(qǐng)求 / 響應(yīng)頭的相關(guān)信息。通過 chrome://net-internals/#events 界面,我們可以看到更多信息。我這里摘錄了訪問我博客的一段日志,并加上了注釋,大家可以對(duì)照前面的介紹看看。
BASHt=2111847 [st = 1] SPDY_SESSION_SYN_STREAM 【客戶端發(fā)送請(qǐng)求】 --> fin = true 【fin 標(biāo)記表示這是當(dāng)前流***一幀】 --> :host: www.imququ.com 【請(qǐng)求頭】 :method: GET :path: /post/devtool-in-chrome32.html :scheme: https :version: HTTP/1.1 accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 accept-encoding: gzip,deflate,sdch accept-language: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4,ja;q=0.2,de;q=0.2,zh-TW;q=0.2 cache-control: max-age=0 cookie: [172 bytes were stripped] dnt: 1 referer: https://imququ.com/ user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.60 Safari/537.36 --> spdy_priority = 0 【優(yōu)先級(jí),0 ***】 --> stream_id = 1 【流id,客戶端創(chuàng)建的流 id 是奇數(shù)】 --> unidirectional = false t=2111980 [st = 134] SPDY_SESSION_SYN_REPLY 【服務(wù)端返回響應(yīng)】 --> fin = false 【fin 為false,表示后續(xù)還有數(shù)據(jù)幀】 --> :status: 200 OK 【響應(yīng)頭】 :version: HTTP/1.1 content-encoding: gzip content-type: text/html; charset=utf8 date: Sat, 15 Mar 2014 06:08:47 GMT server: nginx strict-transport-security: max-age=31536000 x-cache: HIT from cache.ququ x-powered-by: thinkjs-0.4.1 --> stream_id = 1 t=2111981 [st = 135] SPDY_SESSION_RECV_SETTINGS 【各種控制幀】 --> clear_persisted = true --> host = "www.imququ.com:443" t=2111981 [st = 135] SPDY_SESSION_RECV_SETTING --> flags = 0 --> id = 4 --> value = 100 t=2111981 [st = 135] SPDY_SESSION_UPDATE_STREAMS_SEND_WINDOW_SIZE --> delta_window_size = 2147418111 ... t=2112105 [st = 259] SPDY_SESSION_RECV_DATA 【數(shù)據(jù)幀】 --> fin = true 【當(dāng)前流***一幀】 --> size = 0 --> stream_id = 1 t=2112208 [st = 362] SPDY_SESSION_SYN_STREAM 【新的請(qǐng)求】 --> fin = true --> :host: www.imququ.com :method: GET :path: /static/css/theme/the-bizness_datauri_178bc.css :scheme: https :version: HTTP/1.1 accept: text/css,*/*;q=0.1 accept-encoding: gzip,deflate,sdch accept-language: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4,ja;q=0.2,de;q=0.2,zh-TW;q=0.2 cache-control: max-age=0 cookie: [172 bytes were stripped] dnt: 1 if-modified-since: Mon, 10 Feb 2014 15:08:22 GMT pragma: no-cache referer: https://imququ.com/post/devtool-in-chrome32.html user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.60 Safari/537.36 --> spdy_priority = 1 【優(yōu)先級(jí)為1】 --> stream_id = 3 【客戶端創(chuàng)建的流 id 為奇數(shù),且連續(xù)】 --> unidirectional = false ...
如何部署 SPDY 3.1
Chrome 很快就會(huì)移除對(duì) SPDY 2 的支持,F(xiàn)irefox 28 也不支持 SPDY 2 了。如果你還在使用 SPDY 2,是時(shí)候升級(jí)了。
2014 年 2 月 4 日,Nginx 發(fā)布了 1.5.10 版,開始提供對(duì) SPDY 3.1 的支持。下載 nginx ***的 1.5.11 源碼包后,再去 openssl 官網(wǎng)下一個(gè)***的 openssl 庫,就可以編譯了。configure 時(shí)需要啟用 spdy、ssl 模塊,另外需要指定前面下載到的 openssl 庫,這樣才能確保使用***的 ssl:
./configure --with-openssl=/home/jerry/tmp/openssl-1.0.1e/ --with-http_spdy_module --with-http_ssl_module
有了支持 SPDY 3.1 的 nginx,接下來在站點(diǎn)配置里啟用就可以了,由于 SPDY 協(xié)議必須使用 HTTPS,所以端口默認(rèn)是 443,證書什么的也需要提前配好。
BASHserver { server_name www.imququ.com; server_tokens off; listen 443 ssl spdy; ssl_certificate /home/jerry/ssl/server.crt; ssl_certificate_key /home/jerry/ssl/server.key; spdy_headers_comp 6; add_header Strict-Transport-Security max-age=31536000; ... ... }
一切 OK 后,打開 Chrome 的這個(gè)頁面:chrome://net-internals/#spdy,可以查看 SPDY 的使用情況。