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这几年出的糗还少吗,何时能弄稳定呢。

相关文章
相关标签/搜索