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

微博 2017年02月15日

      本文主要对HTTP/2进行介绍、及如何从curl客户端测试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_headers、apache_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,欢迎批评指正。

      ​​​​​