新浪云即将支持HTTP/2协议

鉴于HTTP/2的性能改进及主流浏览器都以支持,新浪云计划在近期对企业用户的部分上传了SSL的证书的域名开放使用HTTP/2功能,此功能默认关闭,需要用户自行开启。

HTTP/2诞生的背景

当前几乎所有互联网上的网页内容传输都采用了HTTP/1.1协议,随着网页内容和样式的发展,HTTP/1.1协议的劣势逐渐明显。

于是在2014年,HTTPbis小组决定开始定制HTTP/2协议。而早在2012年Google就设计了SPDY协议用来解决HTTP/1.1的缺陷,并被用于Google Chrome浏览器中来访问Google的SSL加密服务,据官方说明使用SPDY协议页面加载时间相比于HTTP/1.x减少了64%。因此,HTTPbis便基于SPDY/3草案进行一些修改之后发布了HTTP/2的draft-00。

HTTP/2简介

  • 继续维持HTTP/1.1的模型。客户端基于TCP协议发送请求到服务器。
  • 不改变http://https://协议的URL。
  • 新的二进制格式。

    HTTP/1.1是明文协议,其格式由三部分组成:start line(request line或者status line),header,body。要识别这3部分就要做协议解析,而基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多。二进制则不同,只认0和1的组合。基于这种考虑HTTP/2的协议解析决定采用二进制格式,实现方便且健壮。

    HTTP/2会发送有着不同类型的二进制帧,但他们都有这5个公共字段:Type, Length, Flags, Stream Identifier和frame payload。

    规范中一共定义了10种不同的帧,其中最基础的两种分别对应于HTTP/1.1的DATA和HEADERS。虽然协议的格式和HTTP/1.1完全不同,实际上HTTP/2并没有改变HTTP/1.1的语义,只是把原来HTTP/1.1的header和body部分用frame重新封装了一层而已。

  • 连接的多路复用。

    二进制协议中的Stream Identifier字段就是用作连接共享机制的。一个request对应一个stream并分配一个Identifier,这样一个连接上可以有多个stream,每个stream的frame可以随机的混杂在一起,接收方可以根据Stream Identifier将frame再归属到各自不同的request里面。

  • 支持优先级和依赖。

    HTTP/2里的每个stream都可以设置优先级(Priority)和依赖(Dependency)。优先级高的stream会被server优先处理和返回给客户端,stream还可以依赖其它的sub streams。优先级和依赖都是可以动态调整的。动态调整在有些场景下很有用,假想用户在用你的app浏览商品的时候,快速的滑动到了商品列表的底部,但前面的请求先发出,如果不把后面的请求优先级设高,用户当前浏览的图片要到最后才能下载完成,显然体验没有设置优先级好。同理依赖在有些场景下也有妙用。

  • 头压缩。 HTTP/1.1的header带有大量信息,而且每次都要重复发送,HTTP/2使用HPACK压缩算法来减少需要传输的header大小。通讯双方各自缓存一份header字段列表,既避免了重复header的传输,又减小了需要传输的大小。

  • 重置连接。

    很多使用场景有取消图片下载的功能,对于HTTP/1.1来说最终解决方案都会导致断开连接,下次再发请求就必须重新建立连接。而HTTP/2引入RST_STREAM类型的frame,可以在不断开连接的前提下取消某个request的stream。从而避免浪费带宽和中断已有的连接。

  • 流量控制。

  • 服务器推送。

现状

到目前为止几乎所有的主流浏览器和web服务器都支持HTTP/2,iOS9+也已支持HTTP/2。

可以看出来,连接的多路复用和重置连接使页面加载时长缩短,支持优先级和依赖使用户体验更好,头压缩和重置连接使降低了传输流量。

开启后的注意事项

HTTP/2协议实现中,所有的客户端会把header头中的key转换为小写,如果您的程序中使用了类似以下的代码(以PHP为例)则需要修改后再开启。

PHP中如果直接使用$_SERVER获取请求中的header信息,以HTTP_开头的部分都会被转换成大写。如果程序中所有的header头都是从这里获取的,则不需要修改代码

如果使用了apache_request_headersapache_getenv函数从Apache层获取原始的header信息,且之前的程序中区分了大小写,需要修改成为把所有的key值都改成小写后才能获取。

测试的程序如下,从服务端写一段脚本:

<?php  
var_dump(apache_request_headers());//输出从Apache获取的header信息  
var_dump("------PHP HEADER-----\n");  
var_dump($_SERVER);//输出从$_SERVER中获取的header信息  

用curl测试下自定义header试试,如何让curl支持http2请参考下一小节,有兴趣的可以从新浪云测试。 运行命令:

curl --http2 'https://www.aikaiyuan.com/test.php' -H"A:bBb"  

看到执行的结果如下:

array(18) {  
  ["X-Forwarded-For"]=>
  string(30) "220.181.136.57, 220.181.136.57"
  .....
  ["a"]=>
  string(3) "bBb"
}
string(22) "------PHP HEADER-----  
"
array(46) {  
  ["MEF_PROXY_ADDR"]=>
  string(11) "10.67.15.15"
  .....
  ["HTTP_A"]=>
  string(3) "bBb"
  .....
}

可以清楚的看到实验结果,从apache_request_headers函数中取到的原始值中header值A已经被转换成a了,但是在$_SERVER中之所以还是大写,是因为PHP自身的处理。

用curl测试HTTP/2

如果你也希望可以从curl中测试HTTP/2,可以参考以下的步骤编译一个支持HTTP/2的curl客户端。

从新浪云创建一个Ubuntu应用

访问 http://sae.sina.com.cn/?m=apps&a=create ,语言选择“自定义”,部署方式选择“手工部署”,操作系统选择“Ubuntu”即可,如图所示:

进入终端

进入应用管理面板,选择左侧“容器管理”,进入应用的容器管理,看到操作中有“终端”,进入终端即可。如图:

安装依赖

依次执行以下的命令安装:

安装相关工具:

apt-get install git g++ make binutils autoconf automake autotools-dev libtool pkg-config zlib1g-dev libcunit1-dev libssl-dev libxml2-dev libev-dev libevent-dev libjansson-dev libjemalloc-dev cython python3-dev python-setuptools  

安装nghttp2

git clone https://github.com/tatsuhiro-t/nghttp2.git  
cd nghttp2  
autoreconf -i  
automake  
autoconf  
./configure
make  
sudo make install  

编译安装curl

cd ~  
apt-get install wget  
wget http://curl.haxx.se/download/curl-7.46.0.tar.bz2  
tar -xvjf curl-7.46.0.tar.bz2  
cd curl-7.46.0  
./configure --with-nghttp2=/usr/local --with-ssl
make && make install  
echo '/usr/local/lib' > /etc/ld.so.conf.d/local.conf  
ldconfig  
验证

验证下curl客户端:

root@67994995f68d:/curl-7.46.0# curl --version  
curl 7.46.0 (x86_64-pc-linux-gnu) libcurl/7.46.0 OpenSSL/1.0.2g zlib/1.2.8 nghttp2/1.20.0-DEV  
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp  
Features: IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets  

找个支持HTTP/2的站点(请添加host 61.172.201.142 www.aikaiyuan.com,因为当前新浪云正在灰度)测试一下:

curl --http2 'https://www.aikaiyuan.com/test.php' -H"a:b" >/dev/null -vvv  
---中间部分输出省略---
> GET /test.php HTTP/1.1
> Host: www.aikaiyuan.com
> User-Agent: curl/7.46.0
> Accept: */*
> a:b
> 
{ [5 bytes data]
< HTTP/2.0 200  
< server:nginx  
< date:Wed, 15 Feb 2017 10:14:26 GMT  
< content-type:text/html; charset=UTF-8  
< content-length:3038  
< via:144157  
<  
{ [1262 bytes data]
100  3038  100  3038    0     0   9655      0 --:--:-- --:--:-- --:--:--  9706  
* Connection #0 to host www.aikaiyuan.com left intac

可以看到已经可以使用HTTP/2.0协议发送请求了。

本文作者:微博 @wenqianglee / @lazypeople,欢迎批评指正。