http2-協議協商過程

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這幾年出的糗還少嗎,何時能弄穩定呢。

相關文章
相關標籤/搜索