一 復習與目標
1 復習
-
HTTP1.1存在的問題
-
HTTP2.0要兼容HTTP1.1
-
HTTP2.0的重要概念
- 分幀層
- 二進制:流 消息 幀
- 流的狀態、優先級和並發
- 流量控制
- 服務器推送
- 首部壓縮
-
HTTP2.0的流的建立(HEADERS或PUSH_PROMISE)和數據發送(DATA)
2 目標
- 幀定義
- HTTP2.0流量分析
- Chrome插件:HTTP/2 and SPDY
- WireShark
- 對某些幀進行分析
- HTTP優化
二 幀定義
1 HEADERS
(1)定義
- 長度:16位,代表幀凈荷最大可達65535字節(64KB),只有當Padded為1時才有效。
- 類型:0x01
- 標志:
- End Stream:位1標識最后報頭區塊;一個HEADER幀太長時會分幀傳輸,后續跟着CONTINUATION 幀,如果CONTINUATION幀的END_STREAM標志為1,代表流傳輸結束。但是CONTINUATION幀並不能用於關閉流。
- End Segment:位2標識當前端的最后一幀。
- End Header:位3標識幀包含了整個的報頭塊且后面沒有延續幀。
- Padded:位4標識Pad Length字段會呈現。
- Priority:位6標識專用標記、流依賴及權重字段
- R:保留字段,1位
- 流標志符:31位,唯一標識 HTTP 2.0 的流,流標識符為奇數(不包含1),1為升級協議使用。
- 首部塊:HTTP首部
(2)wireshark抓包
Stream: HEADERS, Stream ID: 25, Length 51, POST /fd/ls/lsp.aspx
Length: 51
Type: HEADERS (1)
Flags: 0x24
.... ...0 = End Stream: False
.... .1.. = End Headers: True
.... 0... = Padded: False
..1. .... = Priority: True
00.0 ..0. = Unused: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0001 1001 = Stream Identifier: 25
1... .... .... .... .... .... .... .... = Exclusive: True
.000 0000 0000 0000 0000 0000 0000 0000 = Stream Dependency: 0
Weight: 219
Header Block Fragment: 83dd870084b958d33f8b625918a10c508ad71a2bf35c830b...
Header: :method: POST
Header: :authority: cn.bing.com
Header: :scheme: https
#......
2 DATA
(1)定義
- 類型為0x0
注:基本上與HEADERS幀差不多,不過多介紹了。
(2)wireshark抓包
Stream: DATA, Stream ID: 25, Length 1316
Length: 1316
Type: DATA (0)
Flags: 0x01
.... ...1 = End Stream: True
.... 0... = Padded: False
0000 .00. = Unused: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0001 1001 = Stream Identifier: 25
Data: ......
3 SETTINGS幀
(1)定義
-
類型為0x4
-
ACK標志:位1表示設置幀被接收端接受並應用。當ACK為1時,幀凈荷必須為空.
-
幀凈荷為幀參數
- SETTINGS_HEADER_TABLE_SIZE(1) :允許發送端通知遠端終端解碼報頭區塊的報頭壓縮表的最大承載量。初始值為4096字節
- SETTINGS_ENABLE_PUSH(2) :服務器推送許可標志,默認開啟。
- SETTINGS_MAX_CONCURRENT_STREAMS(3) :最大流並發數,0代表不允許新建流,默認沒有限制。
- SETTINGS_INITIAL_WINDOW_SIZE(4):初始窗口大小,默認65535,不超過2 ^ 31-1。
(2)wireshark抓包
# 發送設置幀
202.89.233.101 192.168.1.46 HTTP2 123 SETTINGS[0], WINDOW_UPDATE[0]
Stream: SETTINGS, Stream ID: 0, Length 18
Length: 18
Type: SETTINGS (4)
Flags: 0x00
.... ...0 = ACK: False
0000 000. = Unused: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0
Settings - Header table size : 65536
Settings - Max concurrent streams : 1000
Settings - Initial Windows size : 6291456
# 發送設置幀確認
192.168.1.46 202.89.233.101 HTTP2 92 SETTINGS[0]
Stream: SETTINGS, Stream ID: 0, Length 0
Length: 0
Type: SETTINGS (4)
Flags: 0x01
.... ...1 = ACK: True
0000 000. = Unused: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0
4 WINDOW_UPDATE幀
(1)定義
- 類型:0x8
- 標志:無
- 幀凈荷:
- Reserved:1位保留位
- Window Size Increment:最大值位2^31-1
(2)wireshark抓包
Stream: WINDOW_UPDATE, Stream ID: 0, Length 4
Length: 4
Type: WINDOW_UPDATE (8)
Flags: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 1111 0000 0000 0000 0001 = Window Size Increment: 983041
(3)補充
- 流量控制是能作用某個流或整個連接(流標志符號為0,即根流可以限制依賴流)。
- WINDOW_UPDATE可由一個已經發送帶有END_STREAM標記的幀的對等端來發送。這意味着接收端可以在“半封閉(遠程)”或者“關閉”的流上接收WINDOW_UPDATE幀。
- 流量控制窗口用於指示發送端被允許傳輸的字節數(請回想起TCP的阻塞窗口和接受窗口)。
- 流量控制計算不包含幀報頭。
- 流量窗口初始值為65535
- 流量窗口為負值代表禁止發送
- SETTING幀無法修改鏈接狀態的流量控制窗口(即只能設置初始值,無法通過SETTING幀進行修改)
例如,如果客戶端在建立的連接上立即發送60KB的數據,而終端將初始的窗口大小設置成16KB,客戶端將重新計算流量控制窗口的可用空間為-44KB。終端將保持一個負數的流量控制窗口直到窗口更新幀恢復窗口到正數,這個時候客戶端才能恢復數據發送。
5 PING幀
(1)定義
- 長度:固定為0x8
- 類型:0x6
- ACK標志:位1用於標識0為請求,1為響應
- 流標志符:固定為0x0
- 幀凈荷:ping的唯一標識符
(2)wireshark抓包
# 成對出現
192.168.1.46 202.89.233.100 HTTP2 100 PING[0]
Stream: PING, Stream ID: 0, Length 8
Length: 8
Type: PING (6)
Flags: 0x00
.... ...0 = ACK: False
0000 000. = Unused: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0
Ping: 0000000000000001
202.89.233.100 192.168.1.46 HTTP2 100 PING[0]
Stream: PING, Stream ID: 0, Length 8
Length: 8
Type: PING (6)
Flags: 0x00
.... ...1 = ACK: True
0000 000. = Unused: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0
Ping: 0000000000000001
6 RST_STREAM幀
(1)定義
- 長度:固定為0x4
- 類型:0x3
- ACK標志:無
- 流標志符:標識哪個流非正常終結
- 幀凈荷:錯誤碼
- NO_ERROR(0):沒有錯誤,用於給GOWAY幀平滑關閉
- PROTOCOL_ERROR(1):協議錯誤
- INTERNAL_ERROR (2) : 終端遇到意外的內部錯誤。
- FLOW_CONTROL_ERROR (3) : 終端檢測到對等端違反了流量控制
- SETTINGS_TIMEOUT (4) : 終端發送了設置幀,但沒有及時收到響應。
- STREAM_CLOSED (5) : 終端在流半封閉的時候收到幀。
- FRAME_SIZE_ERROR (6) : 終端收到大小超過最大尺寸的幀(2^14 -1)。
- REFUSED_STREAM (7) : 終端拒絕流在它執行任何應用處理之前.
- CANCEL (8) : 終端使用這個標示某個流不再需要。
- COMPRESSION_ERROR (9) : 終端無法維持報頭壓縮上下文的連接
- CONNECT_ERROR (10) : 響應某個連接請求建立的連接被服為異常關閉。
- ENHANCE_YOUR_CALM (11) : 終端檢測出對等端在表現出可能會產生過大負荷的行為。
- INADEQUATE_SECURITY (12) : 基礎傳輸包含屬性不滿足文檔或者終端申明的最小要求。
(2)wireshark抓包
Stream: RST_STREAM, Stream ID: 5, Length 4
Length: 4
Type: RST_STREAM (3)
Flags: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0101 = Stream Identifier: 5
Error: CANCEL (8)
7 PUSH_PROMISE
(1)定義
- 長度:非固定
- 類型:0x04
- 標志:
- END_HEADERS:位3標識幀包含了整個的報頭塊且后面沒有延續幀。
- Padded:位4標識Pad Length字段會呈現。
- 流標志符(Stream Identifier)
- 准備推送的流標志符(Promised-Stream-ID)
- 首部塊:HTTP首部
(2)wireshark抓包
# 由服務器建立流並發送HTTP頭部
Stream: PUSH_PROMISE, Stream ID: 1, Length 166, GET /styles.780f923f35dd43d00653.css
Length: 166
Type: PUSH_PROMISE (5)
Flags: 0x04
.... .1.. = End Headers: True
.... 0... = Padded: False
0000 ..00 = Unused: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0010 = Promised-Stream-ID: 2
Header Block Fragment: 208244976109f54150bbaf0257c4ccacb7248d332000e36c...
Header: :method: GET
# ......
(3)補充
- PUSH_PROMISE為服務器建立流,HEADERS為客戶端建立流。
- 服務器建立的流標識符為偶數(不包含0),0為根流標識符。
- 客戶端 -----HEADERS幀(含priority)-----> 服務器
- 服務器 -----PUSH_PROMISE幀(不含priority)-----> 客戶端,所以一般后續幀為 客戶端 -----PRIORITY幀----> 服務器。
8 PRIORITY幀
(1)定義
- 長度:非固定
- 類型:0x02
- 標志:無
- 保留位和流標志符(Stream Identifier)
- 獨占位(Exclusive):1位,指示流的依賴是專有的
- 依賴流標識符(Stream Dependency)
- 優先級權重:1-256的權重值
(2)wireshark抓包
Stream: PRIORITY, Stream ID: 2, Length 5
Length: 5
Type: PRIORITY (2)
Flags: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0010 = Stream Identifier: 2
1... .... .... .... .... .... .... .... = Exclusive: True
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Dependency: 1
Weight: 109
9 GOWAY幀
(1)定義
- 長度:固定
- 類型:0x07
- 標志:無
- 保留位和流標志符(Stream Identifier)
- 保留位與最后一個流標識符
- 錯誤碼
(2)wireshark抓包
Stream: GOAWAY, Stream ID: 0, Length 8
Length: 8
Type: GOAWAY (7)
Flags: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0011 = Promised-Stream-ID: 3
Error: NO_ERROR (0)
(3)補充
- 通知遠端對等端不要在這個連接上建立新流,即提示拋棄該連接,重新建立一條TCP連接。
- 允許終端優雅的停止接收新的流,但仍可以繼續完成之前已經建立的流的處理。
注:CONTINUTION幀省略,CONTINUTION幀比較簡單。
三 Chrome插件解析HTTP2流量
88: HTTP2_SESSION
Start Time: 2018-12-03 14:36:45.975
# HEADERS幀
t= 10464 [st= 0] HTTP2_SESSION_SEND_HEADERS
--> exclusive = true
--> fin = false
--> has_priority = true
--> :method: POST
:authority: cn.bing.com
:scheme: https
:path: /fd/ls/lsp.aspx
content-length: 1316
origin: https://cn.bing.com
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
content-type: text/xml
accept: */*
referer: https://cn.bing.com/
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7
cookie: _EDGE_V=1; MUID=1EF97277BD3961513FB57ECDBC17605A; MUIDB=1EF97277BD3961513FB57ECDBC17605A; SRCHD=AF=BEHPTB; SRCHUID=V=2&GUID=BD0C76F5F80546D8877C9F6688DDF1AA&dmnchg=1; ENSEARCH=BENVER=1; SRCHUSR=DOB=20181203&T=1543818612000; _EDGE_CD=u=zh-cn; _UR=MC=1; SRCHHPGUSR=CW=1536&CH=711&DPR=1.25&UTC=480&WTS=63679415390&NEWWND=1&NRSLT=-1&SRCHLANG=&AS=1&NNT=1&HAP=0; _EDGE_S=SID=04B092AF518367C83A3F9E1550AD6673; _SS=SID=04B092AF518367C83A3F9E1550AD6673&bIm=978208&HV=1543819002; ULC=P=1D5FE|9:1&H=1D5FE|5:1&T=1D5FE|6:1:8
--> parent_stream_id = 0
--> source_dependency = 259 (HTTP_STREAM_JOB)
--> stream_id = 25
--> weight = 220
# DATA幀
t= 10464 [st= 0] HTTP2_SESSION_SEND_DATA
--> fin = true
--> size = 1316
--> stream_id = 25
# WINDOW_UPDATE幀
t= 10464 [st= 0] HTTP2_SESSION_UPDATE_SEND_WINDOW
--> delta = -1316
--> window_size = 1047260
t= 10501 [st= 37] HTTP2_SESSION_RECV_WINDOW_UPDATE
--> delta = 1316
--> stream_id = 0
t= 10501 [st= 37] HTTP2_SESSION_UPDATE_SEND_WINDOW
--> delta = 1316
--> window_size = 1048576
t= 10541 [st= 77] HTTP2_SESSION_RECV_HEADERS
--> fin = false
--> :status: 204
x-msedge-ref: Ref A: D0007C217F0649D79ED88874FFB61759 Ref B: BJ1EDGE0217 Ref C: 2018-12-03T06:36:55Z
date: Mon, 03 Dec 2018 06:36:55 GMT
--> stream_id = 25
t= 10542 [st= 78] HTTP2_SESSION_RECV_DATA
--> fin = true
--> size = 0
--> stream_id = 25
# PING幀
t= 96465 [st= 86001] HTTP2_SESSION_PING
--> is_ack = false
--> type = "sent"
--> unique_id = 1
t= 96501 [st= 86037] HTTP2_SESSION_PING
--> is_ack = true
--> type = "received"
--> unique_id = 1
# RST_STREAM
t=25836233 [st=1902] HTTP2_SESSION_SEND_RST_STREAM
--> description = ""
--> error_code = "8 (CANCEL)"
--> stream_id = 5
t=222812 [st=212348] HTTP2_SESSION_CLOSE
--> description = "Error 101 reading from socket."
--> net_error = -101 (ERR_CONNECTION_RESET)
t=222813 [st=212349] HTTP2_SESSION_POOL_REMOVE_SESSION
t=222813 [st=212349] -HTTP2_SESSION
四 HTTP優化
(1)基於TCP和TLS優化后
(2)減少DNS查詢
- 盡量不用多域名
- 證書鏈不要太長
(3)減少HTTP重定向
- HTTP 重定向極費時間,特別是不同域名之間的重定向,更加費時
(4)使用CDN(內容分發網絡)
- 把數據放到離用戶地理位置更近的地方,可以顯著減少每次TCP 連接的網絡延
遲,增大吞吐量。
(5)在客戶端緩存資源
# nginx添加響應頭
add_header Cache-Control private,max-age=86400,proxy-revalidate;
- 具體緩存策略參考:HTTP協議探究(二):代理、網關和隧道
(6)傳輸壓縮過的內容
- 速度:brotli > gzip壓縮 > 沒壓縮
# nginx使用ngx_brotli模塊
# gzip壓縮1.1,只有britli失效才有用
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
gzip_comp_level 6;
gzip_http_version 1.1;
# brotli壓縮所有HTTP版本
brotli on;
brotli_comp_level 6;
brotli_buffers 16 8k;
brotli_min_length 20;
brotli_types *;
(7)消除不必要的請求開銷
- 升級HTTP2.0即可
- HTTP2.0能夠壓縮頭部,減少重復頭部傳輸
# nginx配置
listen 443 ssl http2 fastopen=3 reuseport default_server;
(8)利用服務器推送
# nginx配置
location / {
root /home/nginx;
index index.html;
http2_push /styles.389ab69b43b87e6d33f0.css;
# ...
}
參考:
- http2詳解
- HTTP/2 服務器推送(Server Push)教程
- 《Web性能權威指南》
- RFC 7540