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