HTTP2 Server Push的研究
1,HTTP2的新特性。
關于HTTP2的新特性,讀著可以參看我之前的文章,這里就不在多說了,本篇文章主要講一下server push這個特性。
2,Server Push是什么。
簡單來講就是當用戶的瀏覽器和服務器在建立鏈接后,服務器主動將一些資源推送給瀏覽器并緩存起來,這樣當瀏覽器接下來請求這些資源時就直接從緩存中讀取,不會在從服務器上拉了,提升了速率。舉一個例子就是:
假如一個頁面有3個資源文件 index.html , index.css , index.js ,當瀏覽器請求index.html的時候,服務器不僅返回index.html的內容,同時將index.css和index.js的內容push給瀏覽器,當瀏覽器下次請求這2兩個文件時就可以直接從緩存中讀取了。
3,Server Push原理是什么。
要想了解server push原理,首先要理解一些概念。我們知道HTTP2傳輸的格式并不像HTTP1使用文本來傳輸,而是啟用了二進制幀(Frames)格式來傳輸,和server push相關的幀主要分成這幾種類型:
- HEADERS frame(請求返回頭幀):這種幀主要攜帶的http請求頭信息,和HTTP1的header類似。
- DATA frames(數據幀) :這種幀存放真正的數據content,用來傳輸。
- PUSH_PROMISE frame(推送幀):這種幀是由server端發送給client的幀,用來表示server push的幀,這種幀是實現server push的主要幀類型。
- RST_STREAM(取消推送幀):這種幀表示請求關閉幀,簡單講就是當client不想接受某些資源或者接受timeout時會向發送方發送此幀,和PUSH_PROMISE frame一起使用時表示拒絕或者關閉server push。
Note:HTTP2.0相關的幀其實包括 10種幀 ,正是因為底層數據格式的改變,才為HTTP2.0帶來許多的特性,幀的引入不僅有利于壓縮數據,也有利于數據的安全性和可靠傳輸性。
了解了相關的幀類型,下面就是具體server push的實現過程了:
- 由多路復用我們可以知道HTTP2中對于同一個域名的請求會使用一條tcp鏈接而用不同的stream ID來區分各自的請求。
- 當client使用stream 1請求index.html時,server正常處理index.html的請求,并可以得知index.html頁面還將要會請求index.css和index.js。
- server使用stream 1發送PUSH_PROMISE frame給client告訴client我這邊可以使用stream 2來推送index.js和stream 3來推送index.css資源。
- server使用stream 1正常的發送HEADERS frame和DATA frames將index.html的內容返回給client。
- client接收到PUSH_PROMISE frame得知stream 2和stream 3來接收推送資源。
- server拿到index.css和index.js便會發送HEADERS frame和DATA frames將資源發送給client。
- client拿到push的資源后會緩存起來當請求這個資源時會從直接從從緩存中讀取。
下圖表示了整個流程:
4,Server Push怎么用。
既然server push這么神奇,那么我們如何使用呢?怎么設置服務器push哪些文件呢?
首先并不是所有的服務器都支持server push,nginx目前還不支持這個特性,可以在nginx的官方博客上得到證實 https://www.nginx.com/blog/http2-r7/ ,但是Apache和nodejs都已經支持了server push這一個特性,需要說明一點的是server push這個特性是基于瀏覽器和服務器的,所以瀏覽器并沒有提供相應的js api來讓用戶直接操作和控制push的內容,所以只能是通過header信息和server的配置來實現具體的push內容,本文主要以nodejs來說明具體如何使用server push這一特性。
準備工作:下載 nodejs http2 支持,本地啟動nodejs服務。
1. 首先我們使用nodejs搭建基本的server:
var http2 = require('http2');
var url=require('url');
var fs=require('fs');
var mine=require('./mine').types;
var path=require('path');
var server = http2.createServer({
key: fs.readFileSync('./zs/localhost.key'),
cert: fs.readFileSync('./zs/localhost.crt')
}, function(request, response) {
var pathname = url.parse(request.url).pathname;
var realPath = path.join("my", pathname); //這里設置自己的文件名稱;
var pushArray = [];
var ext = path.extname(realPath);
ext = ext ? ext.slice(1) : 'unknown';
var contentType = mine[ext] || "text/plain";
if (fs.existsSync(realPath)) {
response.writeHead(200, {
'Content-Type': contentType
});
response.write(fs.readFileSync(realPath,'binary'));
} else {
response.writeHead(404, {
'Content-Type': 'text/plain'
});
response.write("This request URL " + pathname + " was not found on this server.");
response.end();
}
});
server.listen(443, function() {
console.log('listen on 443');
});
這幾行代碼就是簡單搭建一個nodejs http2服務,打開chrome,我們可以看到所有請求都走了http2,同時也可以驗證多路復用的特性。
這里需要注意幾點:
- 創建http2的nodejs服務必須時基于https的,因為現在主流的瀏覽器都要支持SSL/TLS的http2,證書和私鑰可以自己通過 OPENSSL 生成。
- node http2的相關api和正常的node httpserver相同,可以直接使用。
2. 設置我們的server push:
var pushItem = response.push('/css/bootstrap.min.css', {
request: {
accept: '*/\*'
},
response: {
'content-type': 'text/css'
}
});
pushItem.end(fs.readFileSync('/css/bootstrap.min.css','binary'));
我們設置了bootstrap.min.css來通過server push到我們的瀏覽器,我們可以在瀏覽器中查看:
可以看到,啟動server push的資源timelime非常快,大大加速了css的獲取時間。
這里需要注意下面幾點:
- 我們調用response.push(),就是相當于server發起了PUSH_PROMISE frame來告知瀏覽器bootstrap.min.css將會由server push來獲取。
- response.push()返回的對象時一個正常的ServerResponse,end(),writeHeader()等方法都可以正常調用。
- 這里一旦針對某個資源調用response.push()即發起PUSH_PROMISE frame后,要做好容錯機制,因為瀏覽器在下次請求這個資源時會且只會等待這個server push回來的資源,這里要做好超時和容錯即下面的代碼:
-
try { pushItem.end(fs.readFileSync('my/css/bootstrap.min.css','binary')); } catch(e) { response.writeHead(404, { 'Content-Type': 'text/plain' }); response.end('request error'); } pushItem.stream.on('error', function(err){ response.end(err.message); }); pushItem.stream.on('finish', function(err){ console.log('finish'); });
上面的代碼你可能會發現許多和正常nodejs的httpserver不一樣的東西,那就是stream,其實整個http2都是以stream為單位,這里的stream其實可以理解成一個請求。
- 最后給大家推薦一個老外寫的專門服務http2的node server有興趣的可以嘗試一下。
5,Server Push相關問題。
- 我們知道現在我們web的資源一般都是放在CDN上的,那么CDN的優勢和server push的優勢有何區別呢,到底是哪個比較快呢?這個問題筆者也一直在研究,本文的相關demo都只能算做一個演示,具體的線上實踐還在進行中。
- 由于HTTP2的一些新特性例如多路復用,server push等等都是基于同一個域名的,所以這可能會對我們之前對于HTTP1的一些優化措施例如(資源拆分域名,合并等等)不一定適用。
- server push不僅可以用作拉取靜態資源,我們的cgi請求即ajax請求同樣可以使用server push來發送數據。
- 最完美的結果是CDN域名支持HTTP2,web server域名也同時支持HTTP2。
參考資料:
- HTTP2官方標準: https://tools.ietf.org/html/rfc7540
- 維基百科: https://en.wikipedia.org/wiki/HTTP/2_Server_Push
- https://www.nihaoshijie.com.cn/index.php/archives/651
來自:http://www.alloyteam.com/2017/01/http2-server-push-research/