超文本傳輸協議(英文:HyperText Transfer Protocol,縮寫:HTTP)是互聯網上應用最爲廣泛的一種網絡協議。設計 HTTP 最初的目的是爲了提供一種發佈和接收 HTML 頁面的方法。通過 HTTP 或者 HTTPS 協議請求的資源由統一資源標識符(URI)來標識。
雖然HTTP/1.1穩定運行了十多年了,但HTTP/2來勢洶洶,作爲技術工程師有必要學習一下HTTP/2。今天,阿里雲CDN安防技術專家金九將從歷史、特性、調試、性能四個層面,來全面解析HTTP/2,希望本文可以給你帶來一些啓發。
一、歷史
1、 HTTP/0.9
最早的原型,1991年發佈,該版本極其簡單,只支持 GET 方法,不支持 MIME 類型和各種 HTTP 首部等等。
2、 HTTP/1.0
1996年發佈。HTTP/1.0在HTTP/0.9的基礎之上添加很多方法,各種 HTTP 首部,以及對多媒體對象的處理。
除了GET命令,還引入了POST命令和HEAD命令,豐富了瀏覽器與服務器的互動手段。
任何格式的內容都可以發送。這使得互聯網不僅可以傳輸文字,還能傳輸圖像、視頻、二進制文件。這爲互聯網的大發展奠定了基礎。
HTTP請求和迴應的格式也變了。除了數據部分,每次通信都必須包括頭信息(HTTP header),用來描述一些元數據。
可以說,HTTP/1.0是對HTTP/0.9做了革命性的改變,但HTTP/1.0依然有一些缺點,其主要缺點是每個TCP連接只能發送一個請求,發送數據完畢後連接就關閉,如果還要請求其他資源,就得再新建一個連接。雖然有些瀏覽器爲了解決這個問題,用了一個非標準的Connection頭部,但這個不是標準頭部,各個瀏覽器和服務器實現有可能不一致,因此不是根本解決辦法。
3 、HTTP/1.1
1999年正式發佈。HTTP/1.1是當前主流的 HTTP 協議。完善了之前 HTTP 設計中的結構性缺陷,明確了語義,添加和刪除了一些特性,支持更加複雜的的 Web 應用。
經過了十多年將近20年的發展,這個版本的HTTP協議已經很穩定了,跟HTTP/1.0相比,它新增了很多引人注目的新特性,比如Host協議頭、Range分段請求、默認持久連接、壓縮、分塊傳輸編碼(chunked)、緩存處理等等,至今都大量使用,而且很多軟件依賴這些特性。
雖然HTTP/1.1並不像HTTP/1.0對於HTTP/0.9那樣的革命性,但是也有很多增強,目前主流瀏覽器均默認採用HTTP/1.1。
4、SPDY
SPDY(發音:speedy)協議由Google開發,主要解決 HTTP/1.1 效率不高的問題,於2009年公開,到2016年初結束使命。因爲HTTP/2已經被IETF標準化了,以後各種新版瀏覽器都會支持HTTP/2,Google認爲SPDY已經沒有存在的必要了,接下來的使命由HTTP/2去完成。
**5、HTTP/2**
HTTP/2是最新的HTTP協議,已於2015年5月份正式發佈, Chrome、 IE11、Safari以及Firefox 等主流瀏覽器已經支持 HTTP/2協議。
注意是HTTP/2而不是HTTP/2.0,這是因爲IETF(Internet Engineering Task Force,互聯網工程任務組)認爲HTTP/2已經很成熟了,沒有必要再發布子版本了,以後要是有重大改動就直接發佈HTTP/3。
其實,HTTP/2的前身是SPDY,甚至它倆的目標、原理和基本實現都差不多。IETF組委會中有很多Google工程師,將SPDY推動成爲標準也就不足爲奇了。
HTTP/2不僅優化了性能而且兼容了HTTP/1.1的語義,其幾大特性與SPDY差不多,與HTTP/1.1有巨大區別,比如它不是文本協議而是二進制協議,而且HTTP頭部採用HPACK進行壓縮,支持多路複用、服務器推送等等。
二、特性
1、二進制協議
HTTP/2 採用二進制格式傳輸數據,而非HTTP/1.x的文本格式。消息頭和消息體均採用二進制格式,並稱之爲”幀“(Frame)。Frame二進制基本格式如下(摘自rfc7540#section-4.1):
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
之所以說是基本格式,是因爲所有HTTP/2 Frame都是由該基本格式來封裝,類似於TCP頭,目前有10個Frame,由Type字段來區分,各個Frame都有自己的二進制格式,都封裝Frame Payload中。
其中有兩個重要的Frame:Headers Frame(Type=0x1)和Data Frame(Type=0x0),分別對應HTTP/1.1中的消息頭(Header)和消息體(Body),由此可見語義並沒有太大變化,而是文本格式變成二進制的Frame。二者的轉換和關係如下圖(摘自 《High Performance Browser Networking》):
此外,HTTP/2中還有流(Stream)和消息(Message)的概念,通過Stream Identifier(即流ID)字段來標識,流ID一樣的是同一個流,流中包含消息,這個消息對應HTTP/1.x的請求消息(Request Message)或者響應消息(Response Message),消息是通過幀(Frame)來傳輸的,響應消息比較大,可能由多個Data Frame來傳輸。HTTP/2中流、消息和幀的對應關係如下圖(摘自 《High Performance Browser Networking》):
2、頭部壓縮
HTTP/1.x 每次請求和響應,都會攜帶大量冗餘消息頭信息,比如Cookie和User Agent,基本一樣的內容,每次請求瀏覽器都會默認攜帶,這會浪費很多帶寬資源,也影響了速度。這是因爲HTTP是無狀態協議,每次請求都必須附上所有信息,從而導致了每次請求都帶上大量重複的消息頭。
爲此,HTTP/2做了優化,對消息頭採用HPACK格式進行壓縮傳輸,並對消息頭建立索引表,相同的消息頭只發送索引號,從而提高效率和速度。但付出的代價是客戶端和服務器均維護一個索引表,在如今內存不值錢的時代,這點空間換取時間還是非常值得的。
關於HPACK請參考RFC7541。
3、多路複用
多路複用是指在一個TCP連接裏,客戶端和服務器都可以同時發送多個請求或者響應,對HTTP/1.x來說各個請求和響應都是有嚴格的次序要求,而在HTTP/2中,不用按照次序一一對應,而且併發的多個請求或者響應中任何一個請求阻塞了不會影響其他的請求或者響應,這樣就避免了“隊頭堵塞”。如下圖(摘自 《High Performance Browser Networking》):
4、服務器推送
服務器推送(Server Push)是指在HTTP/2中服務器未經請求可以主動給客戶端推送資源。例如服務端可以主動把 圖片、JS 和 CSS 文件推送給瀏覽器,而不需要瀏覽器解析HTML後再發送這些請求。當瀏覽器解析HTML後這些需要的資源都已經在瀏覽器裏了,大大提高了網頁加載的速度。如下圖(摘自 《High Performance Browser Networking》):
瀏覽器發起請求page.html這個頁面,這個頁面中引用了script.js和style.css,服務器在響應page.html後順便推送了script.js和style.css這兩個文件,這樣瀏覽器解析完page.html後發現引用的script.js和style.css已經在本地了,不需要再發送請求了,這樣就節省了兩次請求和這兩次請求所花的網絡時間,大大提高了網絡性能和用戶體驗。
5、安全
HTTP的安全是由SSL/TLS來保障,也就是HTTPS,其實HTTP/2並不強制要求依賴SSL/TLS,但是,當前主流瀏覽器均只支持基於SSL/TLS的HTTP/2,況且在網絡劫持日益猖獗的互聯網環境下,HTTPS將是未來的趨勢,HTTP/2基於HTTPS也是未來的趨勢,而各大主流瀏覽器在實現HTTP/2之初均只支持SSL/TLS的HTTP/2,可見安全也是HTTP/2的重要特性之一。
三、調試
從原理和目標上看HTTP/2和SPDY差不多,從Nginx官方代碼上看HTTP/2和SPDY的實現也差不多。Nginx官方代碼中已經刪除了spdy模塊的代碼,取而代之的是http2模塊(ngx_http_v2_module)。
1、啓用
1.1、在編譯參數中加入http2模塊(默認已經有ssl模塊了):
```javascript
# git clone https://github.com/alibaba/tengine.git
# cd tengine
# ./configure --prefix=/opt/tengine --with-http_v2_module
# make
# make install
```
1.2、生成測試證書和私鑰
```javascript
# cd /etc/pki/CA/
# touch index.txt serial
# echo 01 > serial
# openssl genrsa -out private/cakey.pem 2048
# openssl req -new -x509 -key private/cakey.pem -out cacert.pem
# cd /opt/tengine/conf
# openssl genrsa -out tengine.key 2048
# openssl req -new -key tengine.key -out tengine.csr
...
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:ZJ
Locality Name (eg, city) []:HZ
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Aliyun
Organizational Unit Name (eg, section) []:CDN
Common Name (e.g. server FQDN or YOUR name) []:www.tengine.com
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
...
# openssl x509 -req -in tengine.csr -CA /etc/pki/CA/cacert.pem -CAkey /etc/pki/CA/private/cakey.pem -CAcreateserial -out tengine.crt
```
1.3、配置http2
```javascript
server {
listen 443 ssl http2;
server_name www.tengine.com;
default_type text/plain;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_certificate tengine.crt;
ssl_certificate_key tengine.key;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:EECDH+AES256:EECDH+3DES:RSA+3DESi:RC4-SHA:ALL:!MD5:!aNULL:!EXP:!LOW:!SSLV2:!NULL:!ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
location / {
return 200 "http2 is ok";
}
}
```
1.4、啓動tengine即可:
```javascript
# /opt/tengine/sbin/nginx -c /opt/tengine/conf/nginx.conf
```
1.5、測試
先綁定/etc/hosts:
127.0.0.1 www.tengine.com
用nghttp工具測試:
```javascript
jinjiu@j9mac ~/work/pcap$ nghttp 'https://www.tengine.com/' -v
[ 0.019] Connected
[ 0.043][NPN] server offers:
* h2
* http/1.1
The negotiated protocol: h2
[ 0.064] recv SETTINGS frame <length flags=0x00, stream_id=0>
(niv=3)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):128]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):2147483647]
[SETTINGS_MAX_FRAME_SIZE(0x05):16777215]
[ 0.064] recv WINDOW_UPDATE frame <length flags=0x00, stream_id=0>
(window_size_increment=2147418112)
[ 0.064] send SETTINGS frame <length flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.064] send SETTINGS frame <length flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.064] send PRIORITY frame <length flags=0x00, stream_id=3>
(dep_stream_id=0, weight=201, exclusive=0)
[ 0.064] send PRIORITY frame <length flags=0x00, stream_id=5>
(dep_stream_id=0, weight=101, exclusive=0)
[ 0.077] send PRIORITY frame <length flags=0x00, stream_id=7>
(dep_stream_id=0, weight=1, exclusive=0)
[ 0.077] send PRIORITY frame <length flags=0x00, stream_id=9>
(dep_stream_id=7, weight=1, exclusive=0)
[ 0.077] send PRIORITY frame <length flags=0x00, stream_id=11>
(dep_stream_id=3, weight=1, exclusive=0)
[ 0.077] send HEADERS frame <length flags=0x25, stream_id=13>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=11, weight=16, exclusive=0)
; Open new stream
:method: GET
:path: /
:scheme: https
:authority: www.tengine.com
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.9.2
[ 0.087] recv SETTINGS frame <length flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.087] recv (stream_id=13) :status: 200
[ 0.087] recv (stream_id=13) server: Tengine/2.2.0
[ 0.087] recv (stream_id=13) date: Mon, 26 Sep 2016 03:00:01 GMT
[ 0.087] recv (stream_id=13) content-type: text/plain
[ 0.087] recv (stream_id=13) content-length: 11
[ 0.087] recv HEADERS frame <length flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
http2 is ok[ 0.087] recv DATA frame <length flags=0x01, stream_id=13>
; END_STREAM
[ 0.087] send GOAWAY frame <length flags=0x00, stream_id=0>
(last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])
```
用chrome瀏覽器測試:
2、抓包分析
從抓包來學習HTTP/2格式是最好的辦法,但HTTP/2又是基於https的,也就是加密的,直接抓包看到的是密文,沒有意義,還好Wireshark提供解密https流量的辦法可以比較方便地調試HTTP/2。
2.1、先導出系統變量$SSLKEYLOGFILE,以OSX系統爲例
```javascript
#bash
echo "\nexport SSLKEYLOGFILE=~/ssl_debug/ssl_pms.log" >> ~/.bash_profile && . ~/.bash_profile
```
2.2、打開Chrome或者Firefox
```javascript
#chrome
open /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome
```
```javascript
#firefox
open /Applications/Firefox.app/Contents/MacOS/firefox
```
用打開的Chrome或者Firefox瀏覽器打開https網站,比如:https://www.taobao.com
然後看看文件~/ssl_debug/ssl_pms.log有沒有內容,有內容就可以用Wireshark解密https數據了。
2.3、Wireshark設置
```javascript
Wireshark->Perferences...->Protocols->SSL
```
啓動抓包:
可見已經能得到HTTP/2的明文數據了。篇幅所限,在此不展開具體細節了,更多HTTP/2二進制協議細節請參考RFC7540。
四、性能
測試機器配置:cache1.cn1, 115.238.23.13, 16核Intel(R) Xeon(R) CPU L5630 @ 2.13GHz,48G內存,萬兆網卡
測試工具:h2load
測試結果:
結論:
1、無論是否keepalive,HTTP/2與SPDY/3.1性能相當,HTTP/2略優。
2、在size爲1k、2k、4k測試結果中保持較低RT情況下QPS也較高,CPU沒有達到瓶頸,加大壓測客戶端數量後QPS有所提高,但RT變大,5xx也變多(這部分數據沒有給出,是測試時記錄的現象)。
在size爲16k、32k、64k、128k、256k的測試結果中CPU達到瓶頸,隨着size變大,QPS降低,RT變高,CPU性能消耗較多的函數是gcm_ghash_clmul。
在size爲512k時網卡達到瓶頸,CPU沒有達到瓶頸。
3、在開啓keepalive的情況下,HTTP/1.1的性能與HTTP/2的性能差距不是很大。但關閉keepalive時HTTP/2的性能比HTTP/1.1更好。
寫在最後
相比之前的傳輸協議,HTTP/2在底層方面做了很多優化。有安全、省時、簡化開發、更好的適應複雜頁面、提供緩存利用率等顯著的優勢,各大公司也已經紛紛開始使用HTTP/2協議了。阿里雲早在去年發佈的CDN6.0服務就已正式支持HTTP/2,訪問速度最高可提升68%。不知不覺中,一個更安全、更可靠、更高速的時代已經悄然而至。