本文首發地址爲-iOS HTTP/2 Server Push 探索 | 李劍飛的博客javascript
當用戶的瀏覽器和服務器在創建連接後,服務器主動將一些資源推送給瀏覽器並緩存起來,這樣當瀏覽器接下來請求這些資源時就直接從緩存中讀取,不會在從服務器上拉了,提高了速率。舉一個例子就是:css
假如一個頁面有3個資源文件index.html,index.css,index.js,當瀏覽器請求index.html的時候,服務器不只返回index.html的內容,同時將index.css和index.js的內容push給瀏覽器,當瀏覽器下次請求這2兩個文件時就能夠直接從緩存中讀取了。html
以下圖所示: 前端
要想了解server push原理,首先要理解一些概念。咱們知道HTTP/2傳輸的格式並不像HTTP1使用文原本傳輸,而是啓用了二進制幀(Frames)格式來傳輸,和server push相關的幀主要分紅這幾種類型:java
(PS:HTTP/2相關的幀其實包括10種幀,正是由於底層數據格式的改變,才爲HTTP/2帶來許多的特性,幀的引入不只有利於壓縮數據,也有利於數據的安全性和可靠傳輸性。)node
瞭解了相關的幀類型,下面就是具體server push的實現過程了:python
查看 HTTP/2 流量的幾種方式jquery
chrome://net-internals/#http2
,使用 Chrome 自帶的 HTTP/2 調試工具; 使用方便,但受限於 Chrome 瀏覽器,對於 Chrome 不支持的 h2c(HTTP/2 Cleartext,沒有部署 TLS 的 HTTP/2)協議無能爲力。同時,這個工具顯示的信息通過了解析和篩選,不夠全面。nghttp2,是一個用 C 實現的 HTTP/2 庫,支持 h2c。它能夠作爲其它軟件的一部分,爲其提供 HTTP/2 相關功能(例如 curl 的 HTTP/2 功能就是用的 nghttp2)。除此以外,它還提供了四個有用的 HTTP/2 工具:ios
先來用 brew 看一下有沒有 nghttp 相關的庫:git
~ brew search nghttp
nghttp2
複製代碼
看來是有 nghttp2 的,再用 brew 看下須要安裝哪些環境:
~ brew info nghttp2
nghttp2: stable 1.21.0 (bottled), HEAD
HTTP/2 C Library
https://nghttp2.org/
Not installed
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/nghttp2.rb
==> Dependencies
Build: sphinx-doc ✘, pkg-config ✔, cunit ✘
Required: c-ares ✘, libev ✘, openssl ✔, libevent ✘, jansson ✘, boost ✘, spdylay ✘
Recommended: jemalloc ✘
==> Requirements
Optional: python3 ✔
==> Options
--with-examples
Compile and install example programs
--with-python3
Build python3 bindings
--without-docs
Don't build man pages --without-jemalloc Build without jemalloc support --HEAD Install HEAD version 複製代碼
看來須要的依賴還挺多。
使用 brew 安裝 nghttp2 :
brew install nghttp2
複製代碼
一切穩當後,nghttp2 提供的幾個工具就能夠直接用了。
nghttp 作爲一個功能完整的 HTTP/2 客戶端,很是適合用來查看和調試 HTTP/2 流量。它支持的參數不少,經過官方文檔或者 nghttp -h 都能查看。最經常使用幾個參數以下:
如下是使用 nghttp 訪問 https://h2o.examp1e.net 的結果。從調試信息中能夠清晰看到 h2c 協商以及 Server Push 的整個過程:
nghttp -nv 'https://h2o.examp1e.net'
[ 0.201] Connected
The negotiated protocol: h2
[ 1.180] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 1.180] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
(dep_stream_id=0, weight=201, exclusive=0)
[ 1.180] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
(dep_stream_id=0, weight=101, exclusive=0)
[ 1.180] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
(dep_stream_id=0, weight=1, exclusive=0)
[ 1.180] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
(dep_stream_id=7, weight=1, exclusive=0)
[ 1.180] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
(dep_stream_id=3, weight=1, exclusive=0)
[ 1.180] send HEADERS frame <length=39, 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: h2o.examp1e.net
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.21.1
[ 1.373] recv SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):16777216]
[ 1.373] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 1.373] recv (stream_id=13) :method: GET
[ 1.373] recv (stream_id=13) :scheme: https
[ 1.373] recv (stream_id=13) :authority: h2o.examp1e.net
[ 1.373] recv (stream_id=13) :path: /search/jquery-1.9.1.min.js
[ 1.373] recv (stream_id=13) accept: */*
[ 1.373] recv (stream_id=13) accept-encoding: gzip, deflate
[ 1.373] recv (stream_id=13) user-agent: nghttp2/1.21.1
[ 1.373] recv PUSH_PROMISE frame <length=59, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=2)
[ 1.373] recv (stream_id=2) :status: 200
[ 1.373] recv (stream_id=2) server: h2o/2.2.0-beta2
[ 1.373] recv (stream_id=2) date: Mon, 10 Apr 2017 06:30:29 GMT
[ 1.373] recv (stream_id=2) content-type: application/javascript
[ 1.373] recv (stream_id=2) last-modified: Thu, 14 May 2015 04:10:14 GMT
[ 1.373] recv (stream_id=2) etag: "55542026-169d5"
[ 1.373] recv (stream_id=2) accept-ranges: bytes
[ 1.373] recv (stream_id=2) x-http2-push: pushed
[ 1.373] recv (stream_id=2) content-length: 92629
[ 1.373] recv HEADERS frame <length=126, flags=0x04, stream_id=2>
; END_HEADERS
(padlen=0)
; First push response header
[ 1.373] recv (stream_id=13) :method: GET
[ 1.373] recv (stream_id=13) :scheme: https
[ 1.373] recv (stream_id=13) :authority: h2o.examp1e.net
[ 1.373] recv (stream_id=13) :path: /search/oktavia-jquery-ui.js
[ 1.373] recv (stream_id=13) accept: */*
[ 1.373] recv (stream_id=13) accept-encoding: gzip, deflate
[ 1.373] recv (stream_id=13) user-agent: nghttp2/1.21.1
[ 1.373] recv PUSH_PROMISE frame <length=33, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=4)
[ 1.373] recv (stream_id=4) :status: 200
[ 1.373] recv (stream_id=4) server: h2o/2.2.0-beta2
[ 1.373] recv (stream_id=4) date: Mon, 10 Apr 2017 06:30:29 GMT
[ 1.373] recv (stream_id=4) content-type: application/javascript
[ 1.373] recv (stream_id=4) last-modified: Thu, 14 May 2015 04:10:14 GMT
[ 1.373] recv (stream_id=4) etag: "55542026-1388"
[ 1.373] recv (stream_id=4) accept-ranges: bytes
[ 1.374] recv (stream_id=4) x-http2-push: pushed
[ 1.374] recv (stream_id=4) content-length: 5000
[ 1.374] recv HEADERS frame <length=28, flags=0x04, stream_id=4>
; END_HEADERS
(padlen=0)
; First push response header
[ 1.374] recv (stream_id=13) :method: GET
[ 1.374] recv (stream_id=13) :scheme: https
[ 1.374] recv (stream_id=13) :authority: h2o.examp1e.net
[ 1.374] recv (stream_id=13) :path: /search/oktavia-english-search.js
[ 1.374] recv (stream_id=13) accept: */*
[ 1.374] recv (stream_id=13) accept-encoding: gzip, deflate
[ 1.374] recv (stream_id=13) user-agent: nghttp2/1.21.1
[ 1.374] recv PUSH_PROMISE frame <length=35, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=6)
[ 1.374] recv (stream_id=6) :status: 200
[ 1.374] recv (stream_id=6) server: h2o/2.2.0-beta2
[ 1.374] recv (stream_id=6) date: Mon, 10 Apr 2017 06:30:29 GMT
[ 1.374] recv (stream_id=6) content-type: application/javascript
[ 1.374] recv (stream_id=6) last-modified: Thu, 14 May 2015 04:10:14 GMT
[ 1.374] recv (stream_id=6) etag: "55542026-34dd6"
[ 1.374] recv (stream_id=6) accept-ranges: bytes
[ 1.374] recv (stream_id=6) x-http2-push: pushed
[ 1.374] recv (stream_id=6) content-length: 216534
[ 1.374] recv HEADERS frame <length=31, flags=0x04, stream_id=6>
; END_HEADERS
(padlen=0)
; First push response header
[ 1.374] recv (stream_id=13) :method: GET
[ 1.374] recv (stream_id=13) :scheme: https
[ 1.374] recv (stream_id=13) :authority: h2o.examp1e.net
[ 1.374] recv (stream_id=13) :path: /assets/style.css
[ 1.374] recv (stream_id=13) accept: */*
[ 1.374] recv (stream_id=13) accept-encoding: gzip, deflate
[ 1.374] recv (stream_id=13) user-agent: nghttp2/1.21.1
[ 1.374] recv PUSH_PROMISE frame <length=24, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=8)
[ 1.374] recv (stream_id=8) :status: 200
[ 1.374] recv (stream_id=8) server: h2o/2.2.0-beta2
[ 1.374] recv (stream_id=8) date: Mon, 10 Apr 2017 06:30:29 GMT
[ 1.374] recv (stream_id=8) content-type: text/css
[ 1.374] recv (stream_id=8) last-modified: Tue, 20 Sep 2016 05:27:06 GMT
[ 1.374] recv (stream_id=8) etag: "57e0c8aa-1586"
[ 1.374] recv (stream_id=8) accept-ranges: bytes
[ 1.374] recv (stream_id=8) x-http2-push: pushed
[ 1.374] recv (stream_id=8) content-length: 5510
[ 1.374] recv HEADERS frame <length=58, flags=0x04, stream_id=8>
; END_HEADERS
(padlen=0)
; First push response header
[ 1.374] recv (stream_id=13) :method: GET
[ 1.374] recv (stream_id=13) :scheme: https
[ 1.374] recv (stream_id=13) :authority: h2o.examp1e.net
[ 1.374] recv (stream_id=13) :path: /assets/searchstyle.css
[ 1.374] recv (stream_id=13) accept: */*
[ 1.374] recv (stream_id=13) accept-encoding: gzip, deflate
[ 1.374] recv (stream_id=13) user-agent: nghttp2/1.21.1
[ 1.374] recv PUSH_PROMISE frame <length=28, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=10)
[ 1.374] recv (stream_id=10) :status: 200
[ 1.374] recv (stream_id=10) server: h2o/2.2.0-beta2
[ 1.374] recv (stream_id=10) date: Mon, 10 Apr 2017 06:30:29 GMT
[ 1.374] recv (stream_id=10) content-type: text/css
[ 1.374] recv (stream_id=10) last-modified: Tue, 20 Sep 2016 05:27:06 GMT
[ 1.374] recv (stream_id=10) etag: "57e0c8aa-8dd"
[ 1.374] recv (stream_id=10) accept-ranges: bytes
[ 1.374] recv (stream_id=10) x-http2-push: pushed
[ 1.374] recv (stream_id=10) content-length: 2269
[ 1.374] recv HEADERS frame <length=27, flags=0x04, stream_id=10>
; END_HEADERS
(padlen=0)
; First push response header
[ 1.374] recv (stream_id=13) :status: 200
[ 1.374] recv (stream_id=13) server: h2o/2.2.0-beta2
[ 1.374] recv (stream_id=13) date: Mon, 10 Apr 2017 06:30:29 GMT
[ 1.374] recv (stream_id=13) link: </search/jquery-1.9.1.min.js>; rel=preload
[ 1.374] recv (stream_id=13) link: </search/oktavia-jquery-ui.js>; rel=preload
[ 1.374] recv (stream_id=13) link: </search/oktavia-english-search.js>; rel=preload
[ 1.374] recv (stream_id=13) link: </assets/style.css>; rel=preload
[ 1.374] recv (stream_id=13) link: </assets/searchstyle.css>; rel=preload
[ 1.374] recv (stream_id=13) cache-control: no-cache
[ 1.374] recv (stream_id=13) content-type: text/html
[ 1.374] recv (stream_id=13) last-modified: Wed, 05 Apr 2017 06:55:14 GMT
[ 1.374] recv (stream_id=13) etag: "58e494d2-1665"
[ 1.374] recv (stream_id=13) accept-ranges: bytes
[ 1.374] recv (stream_id=13) set-cookie: h2o_casper=AmgAAAAAAAAAAAAYxfEYAAABSA; Path=/; Expires=Tue, 01 Jan 2030 00:00:00 GMT; Secure
[ 1.374] recv (stream_id=13) content-length: 5733
[ 1.374] recv HEADERS frame <length=304, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
[ 1.375] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 1.566] recv DATA frame <length=16137, flags=0x00, stream_id=2>
[ 1.567] recv DATA frame <length=5000, flags=0x01, stream_id=4>
; END_STREAM
[ 1.567] recv DATA frame <length=4915, flags=0x00, stream_id=6>
[ 1.766] recv DATA frame <length=2829, flags=0x00, stream_id=8>
[ 1.766] recv DATA frame <length=2269, flags=0x01, stream_id=10>
; END_STREAM
[ 1.766] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=33120)
[ 1.767] recv DATA frame <length=9065, flags=0x00, stream_id=2>
[ 1.970] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 1.970] recv DATA frame <length=2681, flags=0x01, stream_id=8>
; END_STREAM
[ 1.971] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=2>
(window_size_increment=33855)
[ 1.971] recv DATA frame <length=10072, flags=0x00, stream_id=2>
[ 2.172] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 2.172] recv DATA frame <length=4248, flags=0x00, stream_id=2>
[ 2.173] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 2.173] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=34002)
[ 2.173] recv DATA frame <length=4248, flags=0x00, stream_id=2>
[ 2.577] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 2.578] recv DATA frame <length=2829, flags=0x00, stream_id=2>
[ 2.579] recv DATA frame <length=12762, flags=0x00, stream_id=6>
[ 2.777] recv DATA frame <length=2829, flags=0x00, stream_id=2>
[ 2.777] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=6>
(window_size_increment=33241)
[ 2.778] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 3.177] recv DATA frame <length=8505, flags=0x00, stream_id=2>
[ 3.177] recv DATA frame <length=5667, flags=0x00, stream_id=6>
[ 3.177] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=33993)
[ 3.177] recv DATA frame <length=2829, flags=0x00, stream_id=2>
[ 3.177] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 3.378] recv DATA frame <length=2829, flags=0x00, stream_id=2>
[ 3.579] recv DATA frame <length=11343, flags=0x00, stream_id=6>
[ 3.580] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=34002)
[ 3.580] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=2>
(window_size_increment=33984)
[ 3.583] recv DATA frame <length=7086, flags=0x00, stream_id=2>
[ 3.779] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 4.186] recv DATA frame <length=7086, flags=0x00, stream_id=2>
[ 4.186] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 4.186] recv DATA frame <length=2829, flags=0x00, stream_id=2>
[ 4.395] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 4.396] recv DATA frame <length=2829, flags=0x00, stream_id=2>
[ 4.602] recv DATA frame <length=5667, flags=0x00, stream_id=6>
[ 4.602] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=6>
(window_size_increment=33993)
[ 4.602] recv DATA frame <length=2829, flags=0x00, stream_id=2>
[ 4.602] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=33975)
[ 4.808] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 4.809] recv DATA frame <length=6379, flags=0x01, stream_id=2>
; END_STREAM
[ 5.010] recv DATA frame <length=3536, flags=0x00, stream_id=6>
[ 5.420] recv DATA frame <length=8505, flags=0x00, stream_id=6>
[ 5.420] recv DATA frame <length=5667, flags=0x00, stream_id=6>
[ 5.628] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 5.842] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 5.842] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 5.842] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=34002)
[ 5.842] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=6>
(window_size_increment=33281)
[ 6.057] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 6.273] recv DATA frame <length=8505, flags=0x00, stream_id=6>
[ 6.490] recv DATA frame <length=9924, flags=0x00, stream_id=6>
[ 6.490] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 6.706] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 6.706] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=34002)
[ 6.706] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=6>
(window_size_increment=34002)
[ 6.924] recv DATA frame <length=8505, flags=0x00, stream_id=6>
[ 7.141] recv DATA frame <length=8505, flags=0x00, stream_id=6>
[ 7.361] recv DATA frame <length=8505, flags=0x00, stream_id=6>
[ 7.361] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=34020)
[ 7.574] recv DATA frame <length=9924, flags=0x00, stream_id=6>
[ 7.574] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=6>
(window_size_increment=34029)
[ 7.787] recv DATA frame <length=9924, flags=0x00, stream_id=6>
[ 7.787] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 7.998] recv DATA frame <length=7086, flags=0x00, stream_id=6>
[ 8.210] recv DATA frame <length=9924, flags=0x00, stream_id=6>
[ 8.210] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=34011)
[ 8.210] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=6>
(window_size_increment=34011)
[ 8.425] recv DATA frame <length=11343, flags=0x00, stream_id=6>
[ 8.426] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 8.426] recv DATA frame <length=4053, flags=0x01, stream_id=6>
; END_STREAM
[ 8.631] recv DATA frame <length=4443, flags=0x00, stream_id=13>
[ 8.633] recv DATA frame <length=1290, flags=0x01, stream_id=13>
; END_STREAM
[ 8.633] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=10, error_code=NO_ERROR(0x00), opaque_data(0)=[])
複製代碼
固然,咱們也可使用 grep
搜索出來 server push 的相關 stream:
nghttp -nv 'https://h2o.examp1e.net' | grep 'PUSH_PROMISE'
[ 1.582] recv PUSH_PROMISE frame <length=59, flags=0x04, stream_id=13>
[ 1.582] recv PUSH_PROMISE frame <length=33, flags=0x04, stream_id=13>
[ 1.582] recv PUSH_PROMISE frame <length=35, flags=0x04, stream_id=13>
[ 1.582] recv PUSH_PROMISE frame <length=24, flags=0x04, stream_id=13>
[ 1.582] recv PUSH_PROMISE frame <length=28, flags=0x04, stream_id=13>
複製代碼
在大前端的時代背景下,客戶端開發不會點 JavaScript 都快混不下去了,筆者前段時間在我司前端輪崗了兩週,再加上以前也寫過 ReactNative,但仍是感受前端變化之快領人咋舌,革命還沒有結束,同志仍需努力啊。
我們直接上代碼:
var http2 = require('http2');// http2
var url=require('url'); // https://www.npmjs.com/package/url
var fs=require('fs'); // https://www.npmjs.com/package/fs
var mine=require('mine');
var path=require('path'); // 路徑
var server = http2.createServer({
key: fs.readFileSync('./localhost.key'),
cert: fs.readFileSync('./localhost.crt')
}, function(request, response) {
// var pathname = url.parse(request.url).pathname;
var realPath = './push.json' ;//path.join(pathname,"push.json"); //這裏設置本身的文件路徑,這是該次response返回的內容;
var pushArray = [];
var ext = path.extname(realPath);
ext = ext ? ext.slice(1) : 'unknown';
var contentType = mine[ext] || "text/plain";
if (fs.existsSync(realPath)) {
console.log('success')
response.writeHead(200, {
'Content-Type': contentType
});
response.write(fs.readFileSync(realPath,'binary'));
// 注意 push 路徑必須是絕對路徑,這是該次 server push 返回的內容
var pushItem = response.push('/Users/f.li/Desktop/http2-nodeServer/newpush.json', {
response: {
'content-type': contentType
}
});
pushItem.end(fs.readFileSync('/Users/f.li/Desktop/http2-nodeServer/newpush.json','binary'),()=>{
console.log('newpush end')
});
response.end();
} else {
response.writeHead(404, {
'Content-Type': 'text/plain'
});
response.write("This request URL " + realPath + " was not found on this server.");
response.end();
}
});
server.listen(3000, function() {
console.log('listen on 3000');
});
複製代碼
這裏須要注意幾點:
使用 nghttp 測試一下咱們的代碼有沒有進行 server push:
~ nghttp -nv 'https://localhost:3000/'
[ 0.007] Connected
The negotiated protocol: h2
[ 0.029] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>
(niv=0)
[ 0.029] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.029] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.029] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
(dep_stream_id=0, weight=201, exclusive=0)
[ 0.029] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
(dep_stream_id=0, weight=101, exclusive=0)
[ 0.029] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
(dep_stream_id=0, weight=1, exclusive=0)
[ 0.029] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
(dep_stream_id=7, weight=1, exclusive=0)
[ 0.029] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
(dep_stream_id=3, weight=1, exclusive=0)
[ 0.029] send HEADERS frame <length=38, 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: localhost:3000
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.21.1
[ 0.043] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.049] recv (stream_id=13) :status: 200
[ 0.049] recv (stream_id=13) content-type: text/plain
[ 0.049] recv (stream_id=13) date: Tue, 11 Apr 2017 08:34:46 GMT
[ 0.049] recv HEADERS frame <length=34, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
[ 0.049] recv DATA frame <length=35, flags=0x00, stream_id=13>
[ 0.049] recv (stream_id=13) :method: GET
[ 0.049] recv (stream_id=13) :scheme: https
[ 0.050] recv (stream_id=13) :authority: localhost:3000
[ 0.050] recv (stream_id=13) :path: /Users/f.li/Desktop/http2-nodeServer/newpush.json
[ 0.050] recv PUSH_PROMISE frame <length=56, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=2)
[ 0.050] recv DATA frame <length=0, flags=0x01, stream_id=13>
; END_STREAM
[ 0.050] recv (stream_id=2) :status: 200
[ 0.050] recv (stream_id=2) date: Tue, 11 Apr 2017 08:34:46 GMT
[ 0.050] recv HEADERS frame <length=2, flags=0x04, stream_id=2>
; END_HEADERS
(padlen=0)
; First push response header
[ 0.050] recv DATA frame <length=21, flags=0x00, stream_id=2>
[ 0.050] recv DATA frame <length=0, flags=0x01, stream_id=2>
; END_STREAM
[ 0.050] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[])
複製代碼
看到了 PUSH_PROMISE 的幀,說明進行了 server push。
一樣也可使用chrome查看 server push,以下圖所示:
服務端介紹基本完畢。下面咱們來介紹一些 iOS 客戶端對 Server Push 的使用。
Apple 在這方面作的很好,基本實現了客戶端無感調用http/2 server push。可是筆者查看了些許資料,如今只有iOS 10 支持 http/2。
直接上代碼吧:
#import "ViewController.h"
@interface ViewController ()<NSURLSessionDelegate>
@property(nonatomic,strong)NSURLSession *session;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
#pragma mark - Touch
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self urlSession];
}
#pragma mark - 發送請求
- (void)urlSession
{
NSURL *url = [NSURL URLWithString:@"https://localhost:3000"];
//發送HTTPS請求是須要對網絡會話設置代理的
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *dataTask = [_session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
// 收到該次請求後,當即請求下次的內容
[self urlSessionPush];
}];
[dataTask resume];
}
- (void)urlSessionPush
{
NSURL *url = [NSURL URLWithString:@"https://localhost:3000/Users/f.li/Desktop/http2-nodeServer/newpush.json"];
NSURLSessionDataTask *dataTask = [_session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
[dataTask resume];
}
#pragma mark - URLSession Delegate
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler
{
// 這裏還要設置下 plist 中設置 ATS
if (![challenge.protectionSpace.authenticationMethod isEqualToString:@"NSURLAuthenticationMethodServerTrust"])
{
return;
}
NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
{
NSArray *fetchTypes = @[ @"Unknown", @"Network Load", @"Server Push", @"Local Cache"];
for(NSURLSessionTaskTransactionMetrics *transactionMetrics in [metrics transactionMetrics])
{
NSLog(@"protocol[%@] reuse[%d] fetch:%@ - %@", [transactionMetrics networkProtocolName], [transactionMetrics isReusedConnection], fetchTypes[[transactionMetrics resourceFetchType]], [[transactionMetrics request] URL]);
if([transactionMetrics resourceFetchType] == NSURLSessionTaskMetricsResourceFetchTypeServerPush)
{
NSLog(@"Asset was server pushed");
}
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
複製代碼
分別看下服務端和客戶端的Log: 客戶端:
Http2ServerPush[2525:274943] protocol[h2] reuse[0] fetch:Network Load - https://localhost:3000/
Http2ServerPush[2525:274943] {"message":" http2.0 server is ok"}
Http2ServerPush[2525:274943] protocol[h2] reuse[1] fetch:Server Push - https://localhost:3000/Users/f.li/Desktop/http2-nodeServer/newpush.json
Http2ServerPush[2525:274943] Asset was server pushed
Http2ServerPush[2525:274943] {"message":"newPush"}
複製代碼
服務端:
http2-nodeServer npm start
> http2-nodeServer@1.0.0 start /Users/f.li/Desktop/http2-nodeServer
> node index.js
listen on 3000
success
newpush end
複製代碼
看來確實是客戶端發出了兩次請求,可是服務端只響應了一次(該次響應+ server push)