http/2 協議剛剛發佈不久,http1.1的服務器和客戶端依然大量存在,新老協議一定長期共存一段時間。這樣,瀏覽器和服務器就須要協商使用何種協議進行通信。html
主流的方法是使用ALPN或者NPN來作協商。node
Next Protocol Negotiation (NPN)是一個使SPDY在TLS服務器上對使用何種應用層協議進行協商的協議。IETF(h2的標準化組織)拿到這個,確定要改改,而後蓋個章,把它變成標準。名字也改了叫ALPN(Application Layer Protocol Negotiation)。程序員
區別是有的。就在於誰持有會話協議的決定權。ALPN是由客戶端給服務器發送一個協議清單,由服務器來最終選擇一個。而NPN則正好相反。chrome
Node 已經在tls模塊內實現了NPN支持。只要建立tls服務器(createServer),在options參數內傳遞服務器支持的協議清單NPNProtocols,在客戶端鏈接(connect)傳遞NPNProtocols,這樣創建鏈接後,就能夠在socket.npnProtocol內獲得協商的結果。瀏覽器
請看mocha測試用例:服務器
describe('tls', function() { it('npn', function() { var fs = require('fs'); var path = require('path'); var tls = require('tls'); tls.createServer({ key: fs.readFileSync("example/localhost.key"),// 私有鍵 cert: fs.readFileSync("example/localhost.crt"),// 證書 NPNProtocols: ['h2', 'http 1.1','http 1.0'] // 服務器支持協議清單 }, function(socket) { console.log("s1:"+socket.npnProtocol);// 協商結果 }).listen(1111); //client process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; tls.connect({ port: 1111 }, function() { }); tls.connect({ port: 1111 ,NPNProtocols: ['h2'] }, function() { }); tls.connect({ port: 1111, NPNProtocols: ['http 1.1'] }, function() { }); tls.connect({ port: 1111, NPNProtocols: ['http 1.0'] }, function() { }); }) })
輸出結果網絡
my.js scenario tls √ npn (279ms) s1:http/1.1 s1:h2 s1:http 1.1 s1:http 1.0
程序員的代碼經常氣死寫文字的人——由於一堆艱澀文字,變成代碼經常簡單無比。架構
理論上來講,http/2 能夠架構在tls(加密通道)上,也能夠架構在tcp(平文本)上。協議文本也確實沒有限定或者強制使用tls信道。可是,h2的前身是spdy,而spdy是在tls之上的;spdy的主人家google的瀏覽器,chrome也只支持tls;另一家主流瀏覽器firefox也跟進。這就讓架構於tls之上就成爲http/2的事實上的標準。全部協商協議也都支持NPN(tls的一個擴展)。socket
兩家瀏覽器廠商的作法,其實並不是強梁,而是基於現實考量:
1. 大量現存的代理、中介軟件,都假設80端口上跑的是http1.1,而且基於這個假設,對流經它們的流量作出修改。若是h2繼續使用80,極可能和這些修改發生衝突。使用tls(默認爲443端口)就避開了這個衝突的可能性
2. 加密化信道(相比http1.x)對用戶隱私是更好的保護tcp
經過平文本升級協議到h2,依然可能。採用的是現存的http1.1的升級機制。
GET /page HTTP/1.1 Host: server.example.com Connection: Upgrade, HTTP2-Settings Upgrade: h2c HTTP2-Settings: (SETTINGS payload) HTTP/1.1 200 OK Content-length: 243 Content-type: text/html (... HTTP/1.1 response ...) (or) HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: h2c (... HTTP/2 response ...)
由 Upgrade指定升級目標協議, HTTP2-Settings傳遞base64後的settings。若是服務器爲1.1的,那麼返回1.1的響應。不然,就發101 Switching Protocols ,隨後升級爲h2。
由於這個作法比NPN要作一個網絡往返,這個作法(101 Switching Protocols )被不少實現忽略。好比,node-http2就沒有實現(請腦補標準機構的臉色:)。在程序員友好,客戶友好的柔情面紗下面,遇到核心的性能問題,依然是一片叢林景象。
儘管node-http2代碼內,把ALPN的協議協商也有,而且冠冕堂皇的也和NPN同樣:
options.ALPNProtocols = supportedProtocols; options.NPNProtocols = supportedProtocols; ... this._server = https.createServer(options);
可是通過測試,node尚未支持ALPN,有測試用例爲證:
describe('tls', function() { it('alpn', function() { var fs = require('fs'); var path = require('path'); var tls = require('tls'); tls.createServer({ key: fs.readFileSync("example/localhost.key"), cert: fs.readFileSync("example/localhost.crt"), ALPNProtocols: ['h2', 'http 1.1','http 1.0'] }, function(socket) { console.log("s1:"+socket.npnProtocol); }).listen(1111); //client process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; tls.connect({ port: 1111 }, function() { }); tls.connect({ port: 1111 ,ALPNProtocols: ['h2'] }, function() { }); tls.connect({ port: 1111, ALPNProtocols: ['http 1.1'] }, function() { }); tls.connect({ port: 1111, ALPNProtocols: ['http 1.0'] }, function() { }); }) })
根本就沒有協商,怎麼樣都是http/1.1 !
my.js scenario tls √ alpn (307ms) s1:http/1.1 s1:http/1.1 s1:http/1.1 s1:http/1.1
關於Node對alpn的支持,文檔也沒有說起,可是在issue內有提到,它們正在等待openssl實現而且穩定。穩!定!openssl這幾年出的糗還少嗎,何時能弄穩定呢。