HTTP2即將來

如今瀏覽器裏面很大一部分網頁還在使用HTTP1.1做爲主要的網絡通訊協議。 但,這傻逼協議是1999年弄出來的. 距今已經有xx年了, 這些年裏,美國的IETF 以爲這樣不行.我得出來拯救世界了, 在Chrome的倡導下, 借用Chrome的SPDY 來作爲HTTP2的前身,即, HTTP2 是SPDY/3 draft的優優化版. javascript

那,HTTP2 爲何要出現,又解決了HTTP1.1不能解決的什麼事情呢?php

簡而言之就是css

  • H2是一個二進制協議而,H1是超文本協議.傳輸的內容都不是同樣的html

  • H2遵循多路複用即,代替同一host下的內容,只創建一次鏈接. H1不是(傻逼)前端

  • H2可使用HPACK進行頭部的壓縮,H1則不論什麼請求都會發送java

  • H2容許服務器,預先將網頁所須要的資源PUSH到瀏覽器的內存當中.node

接下來,咱們來看看,H2到底有哪些具體的featurenginx

HTTP2的features

首先介紹一下,HTTP2爲何是一種二進制的協議.git

HTTP2 binary

說道H2的二進制,首先得介紹一下H1的超文本協議.HTTP1.1每次在發送請求時,都須要找出 開頭和結尾的每一幀的位置, 而且,在寫入的時候,還須要刪除多餘的空格,以及選擇最優的方式寫入, 而且若是是HTTP+TLS的話,那性能損耗就比較呵呵了,由於TLS自己的握手協議,以及加密的方式,在必定程度上會對文本信息的內容進行處理等等. 這些無疑都給HTTP1.1的速度形成了極大的影響.因此,HTTP2 不採用這種方式來,而,乾脆直接使用二進制. 那,H2是怎樣實現,二進制傳輸呢?
這裏,借Grigorik在velocity 會議上的PPT,來看一看.github

HTTP2

沒錯,H2是安放在應用層的協議,在接受服務器發送的來的請求時,自動將Header 和 Body部分區分開.

HTTP2 多路複用

在H1中,當發送多個請求時, 會有一種head-of-line blocking現象. 也就是咱們常常看見的瀑布流式的加載方式,這樣的加載方式,只能讓資源按照順序一個一個的加載。 有可能形成以下圖的現象:

blocking

前面一個資源內容超級多,而且都是一次性加載完,即便後面有更重要的資源,也須要進行等待.
但在,H2中就沒有這樣的限制了. 他直接會將不一樣的資源,分拆爲細小的二進制幀來進行傳輸.

transfer

固然,你也不必擔憂,每一次是否會傳輸錯誤,由於實際上每一幀裏面的格式爲:

iframe

在傳輸的每一幀裏面,會有以下屬性來進行表示Length, Type, Flags, Stream Identifier, and frame payload.

only one Tcp connection

這個特性是創建在二進制傳輸的多路複用(multiplexed)的機制上的. 簡而言之就是一句話:

  • 一個域只須要一個TCP鏈接

由於在H1的時候,雖然有Connection:keep-alive的特性可讓你的TCP斷開的稍微晚一點. 但這並無什麼x用,由於,H1天生自帶max-connections數, 沒辦法, 爲了加快更多的資源,你只有多開幾個域名來進行鏈接,這樣一方面是域名成本的花銷,還有一方面是維護量太大。這就是著名的 Domain Sharding. 不過,這一切在H2中,都變得特別的SB。。。覺得,H2自己就能夠實現,一個TCP, 資源無上限的特色.
最顯而易見的特性就是: akamai HTTP2的demo.

HTTP2

前面那一坨綠色的就是HTTP1.1寫一下的資源請求,能夠看到最多有8個,在紅線後面是HTTP2請求的資源數.最多(沒有最多) 就一個... 這足以體現HTTP1.1的傻逼特性了。。。
那他實際上,是怎麼作到在一次TCP中,進行多個資源的請求呢?
參考NewCircle Training 講解的multiplexed video.
咱們之前發送HTTP1.1的狀況是:

HTTP1.1

在HTTP2中,咱們請求的方式改變爲:

HTTP2

有同窗可能會問: 他這樣將多個內容放在一個stream裏面進行傳輸,是怎樣保證資源的有序性呢?
問得好!
HTTP2這個特性確實是創建在stream基礎上的, 上面已經提到過,HTTP2將資源劃分爲最小的frame進行傳輸,這樣能夠達到interleave和priority的效果. 每個frame裏面以下圖所示:

iframe

爲了保證order和priority的feature, 因此,HTTP2在每次發送時,須要額外附帶上一些信息:

  • a unique stream ID

  • different priority

固然除了這些基本的優化外,HTTP2在HEADER方面的優化也是下血本的.

HEADER Compression

HEADER的優化,主要仍是因爲HTTP1.1的頭部機制--在每次請求時,都須要將一大堆頭部帶上,甚至帶上cookie這灰常大的內容. 因此,SPDY 以爲這樣不行,而後就是用了GZIP的壓縮方式,但這樣很容易的就被破解而且劫持,致使安全性問題. HTTP2吸收了此次教訓,決定本身開發一套優化方案,即,由於頭部的更替不是很頻繁,那我就在Server端作個緩存唄,在你此次鏈接有效的時間裏面, client就用重複的發送請求頭了. 這就是HTTP2的HPACK壓縮方式.
HPACK壓縮會通過兩步:

  • 傳輸的value,會通過Huffman coding. 一遍來節省資源.

  • 爲了server和client同步, 兩邊都須要保留一份Header list, 而且,每次發送請求時,都會檢查更新

header

ok, 那這樣就有一個問題, 第一次的請求,確定是最慢的.由於他全部的list都須要進行一份初始化操做. 但這是真沒辦法。。。 若是你靠猜Header的方式進行發送的話,就有可能形成相應錯誤的狀況. 咱們在具體細分一下list, 實際上,每個list裏面還分爲static list 和 dynamic list. 二者的區別具體就是:

  • static: 主要用來存儲common header. 好比 method,path等

  • dynamic: 主要用來存儲自定義的協議頭. 好比: custom-name,custom-method等

整個流程,就能夠用下圖來進行表述:

HTTP2

能夠看到,req/res的Header都會存在同一份表裏面,這樣作可能有點傷內存,不過,速度上仍是很是棒的。

這裏,還有幾個額外的點須要說起一下:

  • 全部頭的協議在HTTP2中都沒有發生改變, 緩存仍是 cache-control, etag,last-modifier

  • response Header 所有是小寫.好比 server,status.

  • request Header 也是所有是小寫,不過有幾個特殊狀況. :method, :scheme, :authority, and :path這幾個基本的頭前面須要:做爲pseudo-header fields.

HTTP2 priority

前面說過了,HTTP2的每一幀上帶有必定的相關信息,好比說權重--priority. 另外還有一個叫作依賴--dependence. 即, 假如某個client想要請求 index.html的資源,那麼server會一併返回index.js和index.css的資源回去. 減小client發送更多的請求,至關於一種Server Push的技術,和如今的SSE挺像的.
想要實現這個feature,有兩個基本的標準:

  • 每一個stream須要有一個1~256的數字來表示權重

  • 每一個stream都應該清晰的標明他的依賴有哪些

實際的一個圖就是這樣:

priority

咱們就按照上圖的狀況來講明吧. 若是一個C資源依賴於D資源,那麼D則做爲C的父節點. 而後按照這樣的順序繼續排下去.
若是存在在一個根節點下面存在兩個節點,好比第一個A,B。 那應該怎麼分呢? 這時候,就用到上文提到的priority. 主要A和B上面的數字. A-12,B-4. 將網絡資源--實際上就是帶寬和CPU,化成一塊蛋糕,那麼,在此時,A能夠分到3/4的資源,而B只能分到1/4的資源. 分配 ( allocation ) 好了以後,則便返回數據.( Ps: 在HTTP2中,分數不分數這並不重要,由於HTTP2傳的是二進制,因此,資源不完整是確定的.只是說,那些文件傳的快一些.)
咱們這裏就按照第三個圖來進行解釋一下吧:

  1. 首先D佔用100%的資源進行發送

  2. D發送完了C一樣佔用100%的資源進行發送

  3. 這裏,因爲A佔3/4而B只佔1/4因此,資源按照權重進行分配,而後繼續發送文件直到結束

HTTP2 Server PUSH

這個機制算是 HTTP2 第二大 feature , 即, one-to-many 的機制去請求資源.由於考慮到之前,前端請求資源是經過 document 的解析來實現資源的 fetch . 這種方式有點傻逼... 就是,我知道這個資源是須要加載的,可是我不能一開始在一次請求中發給你,我須要等你要,我纔給. 這樣,就形成了一種溝通上的麻煩.因此, HTTP2 爲了解決這個 bug , 決定開發出一套,能夠實現 Server Push 資源的機制.

server push

這裏,咱們之請求了 page.html ,但實際上經過 push promise. server 自動 push 給咱們了 script.js 和 style.css 兩個文件. 這樣就省去的兩個 request 的開銷.
這種方式,也就是咱們常常看到的 inlining css 和 inlining script. 不過, 使用 HTTP2 這種機制的話,有一下幾個優於 inlining 的特色:

  • push 的資源可以緩存在瀏覽器中

  • 不一樣的網頁可以使用該緩存,而不用從新發起

  • push 的資源是經過 multiplexed 進行傳輸的

  • push 的資源可以進行 priority 標識

  • client 有權取消push 資源的加載

  • push 的資源必須同域

上面具體的介紹了,關於HTTP2具體的feature. 能夠說,上面都是一些理論上的東西,沒有涉及到一些具體的實操. 不過,一旦你深刻事後,你就會發現,實操都是在理論相對完善的時候才作的. 都是一些工程化上的內容, 記一下就ok了. 因此,這裏,咱們繼續深刻的看一下具體的HTTP2的協議--frame的內容

HTTP2 frame 內容

先看一張圖吧:

frame

這和上面那張圖的內容同樣,只是更加清楚了。HTTP2 就是憑藉他來進行全部的信息交流的,地位差很少和TCP的 frame內容同樣的. HTTP2經過設定了length,type,Flags,R,Stream Identifier來標識一個frame. 這些一共佔用了9B的大小. 具體的爲:

  • 用24-bit 的大小來表示 Length --該 frame 承載數據量的多少, 最多能夠放2^24B (~16MB) 大小. 但在具體實踐中,通常的上線設置爲 16KB。 固然,你也能夠手動進行修改.不過,這樣就不能體現小文件,流式傳輸的特色

  • 8-bit 的 type字段 用來表示該frame的類型

  • 8-bit 的 Flags 字段用來代表,該次 frame 包括哪些 type

  • 1-bit 的 R 就是個保留字段,永遠設置爲0. 實際 沒啥用

  • 31-bit 的 identifier 來代表該stream的unique ID.頗有用,該 flag 就是用來確保有序性的 flag.

根據 HTTP2 官方的解釋說, 俺這樣的安排其實頗有深意的,你知道我爲何會把Length放在開頭嗎? 就是爲了讓 parser 解析的更快, 由於當 parser 開始解析時, 首先就知道了,你此次會傳多大的 frame, 而且也知道了你的 type , 那麼其餘的我就把該 frame 分給特定的引擎進行解析就能夠了. 而後我就 skip 一下,調到一下一個 frame 繼續解析. 而後, 完成最終的數據接受.

既然, 不一樣的 type 可以被不一樣的引擎所解析,那麼 type一共有多少種呢?
懶得數了...就直接說吧.(從上到下 重要性下降哈~)

  • DATA: 至關於 message body內容. 即, 返回的響應體內容

  • HEADERS: 就是 相應頭唄...

  • PRIORITY: 和前面內容提到的 priority 同樣, 用來標識該次 frame 的優先順序.

  • RST_STREAM: 用來結束該資源的 signal

  • PUSH_PROMISE: 這個就比較重要了. 這個上文所說的 server PUSH 有很大的關係. 該是用來設置 server 本身發送的相關資源的 flag.

  • SETTINGS: 用來設置 client 和 server 之間 connection 的 相關配置

  • GOAWAY : 用來告訴 server , 中止發送相關資源

  • CONTINUATION: 和 GOWAY 相反,繼續發送相關資源

  • WINDOW_UPDATE: 使用 flow control 對流進行控制.

HTTP2 傳輸過程

HTTP2 一樣是創建在 TCP 鏈接上的, 他一樣也須要發送請求,而且得到響應. 那他第一次發送的內容究竟是什麼呢?
是資源請求嗎? HTML? JS ? CSS ?
actually, No~
HTTP2 在第一次請求的過程中,發送的內容實際是 HEADERS,由於須要在兩端創建一個 virtually list 來存儲頭部,進行HPACK 壓縮. 以下圖:

HEADERS

請仔細查看他的 Type 能夠發現,就是一個 HEADERS。 這也就是上面所說的存儲兩個不一樣的 header table -- static table && dynamic table.

另外, 還有一個點須要補充一下,就是, client 和 server 爲了防止 stream ID 的重複, 作了一個規定: client-initiated stream 只能爲奇數 stream-ID, 而 server-initiated stream 只能爲偶數的 stream-ID.

HTTP2 實踐過程

首先一個協議的出現, 一定是 >=2 之間的溝通. 那針對於 HTTP2 就是 server 和 browser 之間的通訊協議. 因此, 這就要求, HTTP2 的成功實踐, 不只僅 server 支持, 你的瀏覽器也必須支持才行. 不過,就目前來講, 已經很不錯了: can i use

HTTP2

在 Server 端, 支持 http2 其實,要求也很簡單:

  • nginx 版本 >1.10

  • openssl >1.0.2h 便可.

  • 有一個本身的CA證書.

so, 咱們先從 CA 證書提及, 這裏,先安利一下各大雲平臺, 只要你在他那買了一臺服務器, 他那自動回給你提供免費並且正規的 CA 證書, 個人證書就是在騰訊雲送的. 一年換一次便可. 這裏,我就按 TX(騰訊) 上來講. 當你申請成功時, 他會給你一個 zip 文件. 解壓以後,會獲得兩個文件:

  • 證書文件: 1_www.domain.com_cert.crt

  • 私鑰文件: 2_www.domain.com.key

這個兩個文件先放着, 後面有用.
對於, nginx 和 openssl 來講. 通常 server 自帶的版本都是比較低的, 因此, 這裏咱們採起手動編譯的方式.

# 假設當前目錄在 /usr/local/src
// 下載 1.1.0 openssl
wget -O openssl.tar.gz -c https://www.openssl.org/source/openssl-1.1.0.tar.gz
// 解壓
tar zxf openssl.tar.gz
// 更名
mv openssl-1.1.0/ openssl

// 下載 nginx 1.11.4的源碼
wget -c https://nginx.org/download/nginx-1.11.4.tar.gz
tar zxf nginx-1.11.4.tar.gz
cd nginx-1.11.4/

// 設置編譯事後文件的路徑和啓用的模塊
./configure  --prefix=/usr/local/nginx \
--conf-path=/etc/nginx/nginx.conf \
--with-openssl=../openssl \
--with-http_v2_module \
--with-http_ssl_module \
--with-http_gzip_static_module

// 開始編譯
make && sudo make install

這裏, 我直接整理到 gist 裏. 能夠直接下載下來, 使用 . ./http2.sh 執行便可.

設置環境變量

等 nginx 編譯完, 咱們進入 /usr/local/nginx/sbin 裏面. 將 nginx 軟鏈接到 /usr/sbin裏面.

ln -s /usr/local/nginx/sbin/nginx /usr/sbin/nginx

如今, 咱們就能夠在全局當中使用 nginx 命令了.

配置 nginx conf

經過上面的配置, 咱們接着進到 /etc/nginx/nginx.conf 中. 我直接 paste 配置代碼吧:

# 總體的 nginx 配置
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;
    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 16k;
    gzip_comp_level 5;
    gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php;

    include /etc/nginx/sites-available/*.conf;
}

上面的很少說, 主要內容還在 server 裏面. 我這裏使用的是 nginx + nodeJS. 因此, 後面有一層 proxy.

server {
    listen 80;
    # 重定向之前的 http協議
    server_name villainhr.com www.villainhr.com;
    return 301 https://www.villainhr.com$request_uri;
}
server {
    listen 443 ssl http2;
    server_name www.villainhr.com;
    root /var/www/myblog/app;
    ssl on;
    # 設置上面給出的證書文件
    ssl_certificate         /etc/nginx/private/1 _www.villainhr.com_cert.crt;
    ssl_certificate_key     /etc/nginx/private/2 _www.villainhr.com.key;
    # 設置 ssl 鏈接屬性
    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    # 設置 ciphers 套件
    ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
    ssl_prefer_server_ciphers on;

    add_header Strict-Transport-Security max-age=15768000;

    ssl_stapling on;
    ssl_stapling_verify on;
    location / {
          proxy_pass              http://localhost:8000;
        proxy_set_header        Host $host;
        proxy_set_header        X-Forwarded-Proto $scheme;
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    location ~.(js|css|gif|jpg|jpeg|ico|png|bmp|swf|GIF|JPG|JPEG|ICO|PNG|BMP|SWF) $ {
        root /
          root /var/www/myblog/app/public;
          expires 2d;
          add_header  Cache-Control "no-cache";
          add_header  Pragma no-cache;
          log_not_found off;
    }
}

而後, 使用 nginx 直接運行. 若是上面順利的話, 你的 http2 server 也就大功告成了.
若是, 上面有配置錯誤神馬的. 不放心,能夠直接去 mozilla 上面套一個.

相關文章
相關標籤/搜索