为什么 Chrome 又不支持我的 HTTP/2 网站了？
原来是从 Chrome 51 开始，在 2016 年 5 月 31 日之前，对支持 NPN 协商协议的 HTTP/2 网站还会采用 HTTP/2 访问；而之后就只支持 ALPN 协商协议的 HTTP/2 网站了
Chrome HTTP/2
39.159.16.79
&quot;插件好像好久没亮了&quot;，那个插件叫什么名字？<br />
从头到翻到尾就没看到那个插件是什么…… (´・ω・`)
kashu [Chromium 66.0|Xubuntu 14.04]
2016-11-06T14:00:27.000
115.225.27.235
Debian jessie backports 已经提供 openssl 1.0.2 了。
ivmm [Chrome 55.0|Mac 10.12]
2016-11-06T14:01:39.000
114.253.21.176
第一句。。。
linux [Chrome 54.0|Mac 10.11]
2016-11-06T16:40:24.000
39.159.16.79
额……看到了，原来是，哈哈……
kashu [Chromium 66.0|Xubuntu 14.04]
2016-11-06T17:45:55.000
222.171.207.83
请教一下，想学linux，能不能推荐本书籍，或其他学习方式，谢谢
来自黑龙江哈尔滨的 Chrome Mobile 46.0|Android 6.0 用户
2016-11-06T17:59:18.000
223.81.193.126
我也装上那个扩展试试。
绿色圣光 [Firefox 45.0|GNU/Linux]
2016-11-06T21:04:19.000
202.107.194.180
鸟哥的linux私房菜基础篇,
来自浙江杭州的 Firefox 45.0|GNU/Linux 用户
2016-11-07T15:13:12.000
原来是从 Chrome 51 开始，在 2016 年 5 月 31 日之前，对支持 NPN 协商协议的 HTTP/2 网站还会采用 HTTP/2 访问；而之后就只支持 ALPN 协商协议的 HTTP/2 网站了
昨晚偶尔清理 Chrome 插件时发现我的 “[HTTP/2 and SPDY indicator](https://chrome.google.com/webstore/detail/http2-and-spdy-indicator/mpbpobfflnpcgagjijhmgnchggcjblin)”插件好像好久没亮了。这个插件在你访问到一个支持 HTTP/2 （或之前的 SPDY 协议）的网站时会点亮，而我明明记得之前专门让 <https://linux.cn/> 支持了 HTTP/2 。


我的第一反应是不是这个插件有问题了？于是打开 Chrome 调试工具，然后发现，真的是请求和响应都是 HTTP/1.1 哎！


经过一番研究，原来是从 Chrome 51 开始，在 2016 年 5 月 31 日之前，对支持 NPN 协商协议的 HTTP/2 网站还会采用 HTTP/2 访问；而之后就只支持 ALPN 协商协议的 HTTP/2 网站了——而目前 ALPN 协议仅被鲜少有发行版支持 openssl-1.0.2 支持。


![](/data/attachment/album/201611/06/130031xmoaa0lutlcrualu.jpg)


### 发生了什么？


#### 服务器端


我们知道，最初的 Web 访问协议是 HTTP/1，包括以前的 HTTP/1.0 和现在大部分网站采用的 HTTP/1.1（HTTP/0.9 是试验性协议，已经废弃）。但是随着 Web 应用越来越复杂，之前的 HTTP/1.x 协议就看起来不能满足日益庞杂的 Web 服务需求了。比如说，明文请求、请求复用等问题。因此，谷歌就开发了一个新的传输层协议，名为 SPDY。由于这个新的协议用了的人都说好，因此谷歌就把这个协议提交到了 IETF，然后大家觉得，SPDY 这名字不好听（SPDY 是谷歌的注册商标），就干脆叫 [HTTP/2](https://tools.ietf.org/html/rfc7540) 吧！


SPDY 协议是基于 SSL/TLS 的，谷歌开发了一个名为<ruby> <a href="https://tools.ietf.org/id/draft-agl-tls-nextprotoneg-04.html">  下一代协议协商 </a> <rp>  （ </rp> <rt>  Next Protocol Negotiation </rt> <rp>  ） </rp></ruby>（NPN）的 SSL/TLS 扩展，用于在客户端连接服务器时协商是否采用 HTTP/2 协议。SPDY 协议是由 Web 服务器所实现支持的，而 NPN 则是由 OpenSSL 等 SSL 实现支持的。


但是，随着 SPDY 被提交到 IETF，然后变成了 HTTP/2 协议，谷歌也放弃了 SPDY 的开发，全力投入到了 HTTP/2 的开发中，之前所采用 NPN 也被一种新的协商协议 ALPN ——<ruby> <a href="https://tools.ietf.org/html/rfc7301">  应用层协议协商 </a> <rp>  （ </rp> <rt>  Application-Layer Protocol Negotiation </rt> <rp>  ） </rp></ruby>所替代。NPN 和 ALPN 是不兼容的，它们的主要不同是：


* NPN 是服务器发送所支持的协议列表，由客户端进行选择。而 ALPN 则是客户端发送该列表，由服务端选择。
* 在 NPN 中，最终的选择结果是在 Change Cipher Spec 之后发送给服务端的，也就是说是被加密了的。而在 ALPN 中，所有的协商都是明文的。


这样做的好处主要是安全性方面的考虑，但是这造成了一个问题就是，NPN 已经广泛地被 OpenSSL 支持，而 ALPN 则目前只有最新的 openssl-1.0.2 才支持。当前的几个主流 Linux 发行版的 OpenSSL 版本以及支持的协商协议如下：




| Linux 发行版 | OpenSSL 版本 | 所支持的协商协议 |
| --- | --- | --- |
| CentOS/Oracle Linux/RHEL 5.10+ | 0.9.8e | 不支持 |
| CentOS/Oracle Linux/RHEL 6.5+, 7.0+ | 1.0.1e | NPN |
| Ubuntu 12.04 LTS | 1.0.1 | NPN |
| Ubuntu 14.04 LTS | 1.0.1f | NPN |
| *Ubuntu 16.04 LTS* | *1.0.2g* | *ALPN 和 NPN* |
| Debian 7.0 | 1.0.1e | NPN |
| Debian 8.0 | 1.0.1k | NPN |


从上面我们可以看到，基本上所有的服务器级的 Linux 发行版都不支持 OpenSSL 及 ALPN，唯一支持的 Ubuntu 16.04 LTS 显然用的不会很多。不要小看这 0.0.1 的版本差异，对于别的软件来说这 0.0.1 的差异基本上可以忽略，但是对于 OpenSSL 来说，那就是两个版本代际。OpenSSL 是个相当底层的库，很多重要的软件都依赖于它，因此各个发行版在升级 OpenSSL 时采用的态度是相当保守，比如我们可以看看 CentOS 系统中有哪些软件使用了 OpenSSL：



```
$ lsof | grep libssl | awk '{print $1}' | sort | uniq
anvil
fail2ban
gdbus
gmain
httpd
postfix
mysqld
NetworkManager
nginx
php-fpm
puppet
sshd
sudo
tuned
zabbix_agent
```

没有经过足够的测试，Linux 发行版是不会在产品级（服务器级）的环境中随便升级的。为了解决旧版本（1.0.1）中的安全问题，他们宁可将新的版本（1.0.2）中安全修复移植回旧版本，也不会升级到有新功能的新版本（1.0.2），这就是你见到了各种 1.0.1e、1.0.1k 这样的版本号的原因。


当然，你可以自己编译一个最新 OpenSSL 替代你系统中的 openssl-1.0.1，但是我想你不会这样做的，是吧？


顺便提一句，NPN 和 ALPN 可以并存，但是会客户端会优先选择 ALPN。


#### 浏览器端（Chrome）


从 Chrome 51 开始，谷歌就去掉了对 SPDY 的支持，不过这不是个事，因为不但使用 SPDY 的 Web 服务器比较少，而且从 SPDY 升级到 HTTP/2 也很简单，这方面 Nginx、Apache 等服务器的配置都很简单。


但不幸的是，在 Chrome 51 中，谷歌也去掉了对 NPN 的支持！如果你的 Web 服务器使用的是 openssl-1.0.2 以下的版本，不支持 ALPN 协商，那么 Chrome 51 及以后版本就会以 HTTP/1 协议访问你的网站。


谷歌对放弃 NPN 支持做了一个[简短的解释](https://bugs.chromium.org/p/chromium/issues/detail?id=527066)，但是不管怎么说，NPN 协议在 Chrome 51 之后的版本不会再次回来了。而另一方面，OpenSSL 在 2016 年 12 月 31 日之后也不会继续发布 openssl-1.0.1 系列的新版本了，安全修复到此为止。


而在这种情况下，你原本支持 HTTP/2 的网站通过连接复用等 HTTP/2 所提供的新特性，在 Chrome 下访问取得了不错的体验，而现在又跌回了之前的残旧状态。


### 怎么办呢？


有几种办法：


#### 换浏览器


山不来就我，我去就山。Chrome 51+ 不支持带 NPN 的 HTTP/2 网站，作为浏览者，你可以使用其它的浏览器，比如 Safari、Edge 之类的。这样，你就可以用新的协议来访问世界上那 [10% 支持 HTTP/2 的 Web 服务器](https://w3techs.com/technologies/details/ce-http2/all/all)了。


但是，作为服务器运营者，你却不能忽视[高达 50% 以上的 Chrome 用户](https://www.netmarketshare.com/browser-market-share.aspx?qprid=0&qpcustomd=0)。


#### 换服务器


如上面所示，Ubuntu 16.04 LTS 是目前唯一官方支持 openssl-1.0.2 的 Linux 发行版，如果你一直采用 Ubuntu 做服务器，考虑一下升级吧。LTS 版本的支持期长达五年。


当然，在产品环境中，即便你是 Ubuntu 服务器，更新版本也是一件重大事宜，宜慎思之。


#### 重新编译


既然换服务器不是一个好的选择，那你还有一个方案，就是使用新的 openssl-1.0.2 源代码重新编译你的 Web 服务器，比如 nginx。


下面我简单介绍一下如何用 openssl-1.0.2 来编译 nginx。（1.0.2 系列的最新版本是 1.0.2j，当然你要非用 1.1.0，我也无话可说……）


首先下载并解压 openssl-1.0.2j：



```
# wget https://www.openssl.org/source/openssl-1.0.2j.tar.gz
# tar -zxvf openssl-1.0.2j.tar.gz
```

然后在编译 nginx 的时候使用 `--with-openssl=../openssl-1.0.2j` 选项以及你的其它选项：



```
./configure --with-openssl=../openssl-1.0.2j --with-http_v2_module --with-http_ssl_module
```

配置并编译之后，你可以用 `nginx -V`来看一下你的 nginx 中的 OpenSSL 版本。


这种自行编译的好处是灵活性高，但是你需要随时注意各个组件是否有严重的安全漏洞，并在出了修复版本之后重新编译。


#### 容器


除了自己编译之外，如果你的系统环境中已经有了容器支持，你还可以在容器中运行一个 Ubuntu 16.04 LTS，并将 Web 服务器运行在其中。


### 总结


以上就是 HTTP/2 和 Chrome 之间的故事，你准备去升级 HTTP/2 支持了吗？要知道相比 HTTP/2 的访问体验，你肯定不会想再回到 HTTP/1 了。

