关于 iOS HTTP2.0 的一次学习实践

谈谈 HTTP/2 的协议协商机制

2016/04/16 · 基础技术 ·
HTTP/2

本文作者: 伯乐在线
JerryQu
。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者

文章目录

在过去的几个月里,我写了很多有关 HTTP/2
的文章,也做过好几场相关分享。我在向大家介绍 HTTP/2
的过程中,有一些问题经常会被问到。例如要部署 HTTP/2 一定要先升级到 HTTPS
么?升级到 HTTP/2 之后,不支持 HTTP/2
的浏览器还能正常访问么?本文重点介绍 HTTP/2
的协商机制,明白了服务端和客户端如何协商出最终使用的 HTTP
协议版本,这两个问题就迎刃而解了。

图片 1

HTTP Upgrade

为了更方便地部署新协议,HTTP/1.1 引入了 Upgrade
机制,它使得客户端和服务端之间可以借助已有的 HTTP
语法升级到其它协议。这个机制在 RFC7230 的「6.7
Upgrade
」这一节中有详细描述。

要发起 HTTP/1.1 协议升级,客户端必须在请求头部中指定这两个字段:

Connection: Upgrade Upgrade: protocol-name[/protocol-version]

1
2
Connection: Upgrade
Upgrade: protocol-name[/protocol-version]

客户端通过 Upgrade
头部字段列出所希望升级到的协议和版本,多个协议之间用 ,(0x2C,
0x20)隔开。除了这两个字段之外,一般每种新协议还会要求客户端发送额外的新字段。

如果服务端不同意升级或者不支持 Upgrade
所列出的协议,直接忽略即可(当成 HTTP/1.1 请求,以 HTTP/1.1
响应);如果服务端统一升级,那么需要这样响应:

HTTP/1.1 101 Switching Protocols Connection: upgrade Upgrade:
protocol-name[/protocol-version] [… data defined by new protocol
…]

1
2
3
4
5
HTTP/1.1 101 Switching Protocols
Connection: upgrade
Upgrade: protocol-name[/protocol-version]
 
[… data defined by new protocol …]

可以看到,HTTP Upgrade 响应的状态码是
101,并且响应正文可以使用新协议定义的数据格式。

如果大家之前使用过 WebSocket,应该已经对 HTTP Upgrade
机制有所了解。下面是建立 WebSocket 连接的 HTTP 请求:

GET ws://example.com/ HTTP/1.1 Connection: Upgrade Upgrade: websocket
Origin: http://example.com Sec-WebSocket-Version: 13 Sec-WebSocket-Key:
d4egt7snxxxxxx2WcaMQlA== Sec-WebSocket-Extensions: permessage-deflate;
client_max_window_bits

1
2
3
4
5
6
7
GET ws://example.com/ HTTP/1.1
Connection: Upgrade
Upgrade: websocket
Origin: http://example.com
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: d4egt7snxxxxxx2WcaMQlA==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

这是服务端同意升级的 HTTP 响应:

HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket
Sec-WebSocket-Accept: gczJQPmQ4Ixxxxxx6pZO8U7UbZs=

1
2
3
4
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: gczJQPmQ4Ixxxxxx6pZO8U7UbZs=

在这之后,客户端和服务端之间就可以使用 WebSocket
协议进行双向数据通讯,跟 HTTP/1.1 没关系了。可以看到,WebSocket
连接的建立就是典型的 HTTP Upgrade 机制。

显然,这个机制也可以用做 HTTP/1.1 到 HTTP/2 的协议升级。例如:

GET / HTTP/1.1 Host: example.com Connection: Upgrade, HTTP2-Settings
Upgrade: h2c HTTP2-Settings:

1
2
3
4
5
GET / HTTP/1.1
Host: example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings:

在 HTTP Upgrade 机制中,HTTP/2 的协议名称是 h2c,代表 HTTP/2
ClearText。如果服务端不支持 HTTP/2,它会忽略 Upgrade 字段,直接返回
HTTP/1.1 响应,例如:

HTTP/1.1 200 OK Content-Length: 243 Content-Type: text/html …

1
2
3
4
5
HTTP/1.1 200 OK
Content-Length: 243
Content-Type: text/html
 

如果服务端支持 HTTP/2,那就可以回应 101
状态码及对应头部,并且在响应正文中可以直接使用 HTTP/2 二进制帧:

HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: h2c [
HTTP/2 connection … ]

1
2
3
4
5
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
 
[ HTTP/2 connection … ]

以下是通过 HTTP Upgrade 机制将 HTTP/1.1 升级到 HTTP/2 的 Wireshark
抓包(两张图可以对照来看):

图片 2

图片 3

根据 HTTP/2 协议中的描述,额外补充几点:

  • 41 号包中,客户端发起的协议升级请求中,必须通过 HTTP2-Settings
    指定一个经过 Base64 编码过的 HTTP/2 SETTINGS 帧;
  • 45 号包中,服务端同意协议升级,响应正文中必须包含 HTTP/2 SETTING
    帧(二进制格式,不需要 Base64 编码);
  • 62 号包中,客户端可以开始发送各种 HTTP/2 帧,但第一个帧必须是 Magic
    帧(内容固定为 PRI * HTTP/2.0rnrnSMrnrn),做为协议升级的最终确认;

HTTP Upgrade
机制本身没什么问题,但很容易受网络中间环节影响。例如不能正确处理
Upgrade 头部的代理节点,很可能造成最终升级失败。之前我们统计过
WebSocket 的连通情况,发现大量明明支持 WebSocket
的浏览器却无法升级,只能使用降级方案。

前面的文章也提到了目前的移动端网络常见性能问题,以及对应的优化策略,如果把HTTP1.1
替换为 HTTP2.0,可以说是网络性能优化的一步大棋。这几天对 iOS HTTP2.0
进行了简单的调研、测试,在此做个简单的总结

ALPN 扩展

HTTP/2 协议本身并没有要求它必须基于
HTTPS(TLS)部署,但是出于以下三个原因,实际使用中,HTTP/2 和 HTTPS
几乎都是捆绑在一起:

  • HTTP 数据明文传输,数据很容易被中间节点窥视或篡改,HTTPS
    可以保证数据传输的保密性、完整性和不被冒充;
  • 正因为 HTTPS 传输的数据对中间节点保密,所以它具有更好的连通性。基于
    HTTPS 部署的新协议具有更高的连接成功率;
  • 当前主流浏览器,都只支持基于 HTTPS 部署的 HTTP/2;

如果前面两个原因还不足以说服你,最后这个绝对有说服力,除非你的 HTTP/2
服务只打算给自己客户端用。

下面介绍在 HTTPS 中,浏览器和服务端之间怎样协商是否使用 HTTP/2。

基于 HTTPS 的协议协商非常简单,多了 TLS 之后,双方必须等到成功建立 TLS
连接之后才能发送应用数据。而要建立 TLS 连接,本来就要进行 CipherSuite
等参数的协商。引入 HTTP/2 之后,需要做的只是在原本的协商机制中把对 HTTP
协议的协商加进去。

Google 在 SPDY 协议中开发了一个名为 NPN(Next Protocol
Negotiation,下一代协议协商)的 TLS 扩展。随着 SPDY 被 HTTP/2 取代,NPN
也被官方修订为 ALPN(Application Layer Protocol
Negotiation,应用层协议协商)。二者的目标和实现原理基本一致,这里只介绍后者。如图:

图片 4

可以看到,客户端在建立 TLS 连接的 Client Hello 握手中,通过 ALPN
扩展列出了自己支持的各种应用层协议。其中,HTTP/2 协议名称是 h2

图片 5

如果服务端支持 HTTP/2,在 Server Hello 中指定 ALPN 的结果为 h2
就可以了;如果服务端不支持 HTTP/2,从客户端的 ALPN
列表中选一个自己支持的即可。

并不是所有 HTTP/2 客户端都支持 ALPN,理论上建立 TLS
连接后,依然可以再通过 HTTP Upgrade
进行协议升级,只是这样会额外引入一次往返。

本文的大概思路是介绍 HTTP1.1 的弊端、HTTP2.0 的优势、HTTP2.0
的协商机制、iOS 客户端如何接入
HTTP2.0,以及如何对其进行调试。主要还是加深记忆、方便后期查阅,文末的资料相比本文或许是更有价值的。

小结

看到这里,相信你一定可以很好地回答本文开头提出的问题。

HTTP/2 需要基于 HTTPS 部署是当前主流浏览器的要求。如果你的 HTTP/2
服务要支持浏览器访问,那就必须基于 HTTPS
部署;如果只给自己客户端用,可以不部署
HTTPS(这个页面列举了很多支持
h2c 的 HTTP/2 服务端、客户端实现)。

支持 HTTP/2 的 Web Server 基本都支持 HTTP/1.1。这样,即使浏览器不支持
HTTP/2,双方也可以协商出可用的 HTTP 版本,没有兼容性问题。如下表:

浏览器 服务器 协商结果
不支持 HTTP/2 不支持 HTTP/2 不协商,使用 HTTP/1.1
不支持 HTTP/2 支持 HTTP/2 不协商,使用 HTTP/1.1
支持 HTTP/2 不支持 HTTP/2 协商,使用 HTTP/1.1
支持 HTTP/2 支持 HTTP/2 协商,使用 HTTP/2

当然,本文讨论的是通用情况。对于自己实现的客户端和服务端,如果打算使用
HTTP/2 ClearText,由于 HTTP Upgrade
协商会增加一次往返,可以要求双方必须支持 HTTP/2,直接发送 HTTP/2
数据,不走协商。

打赏支持我写出更多好文章,谢谢!


打赏作者

HTTP 1.1

  • 虽然 HTTP1.1 默认是开启 Keep-Alive
    长连接的,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点,但是依然存在
    head of line
    blocking,如果出现一个较差的网络请求,会影响后续的网络请求。为什么呢?如果你发出1、2、3
    三个网络请求,那么 Response 的顺序 2、3
    要在第一个网络请求之后,以此类推

  • 针对同一域名,在请求较多的情况下,HTTP1.1
    会开辟多个连接,据说浏览器一般是6-8
    个,较多连接也会导致延迟增大,资源消耗等问题

  • HTTP1.1 不安全,可能存在被篡改、被窃听、被伪装等问题。当然,前阵子
    Apple 推广 HTTPS 的时候,相信很多人已经接入 HTTPS

  • HTTP 的头部没有压缩,header
    的大小也是传输的负担,带来更多的流量消耗和传输延迟。并且很多 header
    是相同的,重复传输是没有必要的。

  • 服务端无法主动推送资源到客户端

  • HTTP1.1的格式是文本格式,基于文本做一些扩展、优化相对比较困难,但是文本格式易于阅读和调试,但HTTPS之后,也变成二进制格式了,这个优势也不复存在

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

图片 6
图片 7

1 赞 1 收藏
评论

HTTP2.0

在 HTTP2.0中,上面的问题几乎都不存在了。HTTP2.0 的设计来源于 Google 的
SPDY 协议,如果对 SPDY 协议不了解的话,也可以先对 SPDY
进行了解,不过这不影响继续阅读本文

  • HTTP 2.0
    使用新的二进制格式:基本的协议单位是帧,每个帧都有不同的类型和用途,规范中定义了10种不同的帧。例如,报头(HEADERS)和数据(DATA)帧组成了基本的HTTP
    请求和响应;其他帧例如 设置(SETTINGS),窗口更新(WINDOW_UPDATE),
    和推送承诺(PUSH_PROMISE)是用来实现HTTP/2的其他功能。那些请求和响应的帧数据通过流来进行数据交换。新的二进制格式是流量控制、优先级、server
    push等功能的基础。

流(Stream):一个Stream是包含一条或多条信息、ID和优先级的双向通道

消息(Message):消息由帧组成

帧(Frame):帧有不同的类型,并且是混合的。他们通过stream
id被重新组装进消息中

图片 8

  • 多路复用:也就是连接共享,刚才说到 HTTP1.1的 head of line
    blocking,那么在多路复用的情况下,blocking 已经不存在了。每个连接中
    可以包含多个流,而每个流中交错包含着来自两端的帧。也就是说同一个连接中是来自不同流的数据包混合在一起,如下图所示,每一块代表帧,而相同颜色块来自同一个流,每个流都有自己的
    ID,在接收端会根据 ID
    进行重装组合,就是通过这样一种方式来实现多路复用。

图片 9

  • 单一连接:刚才也说到 1.1 在请求多的时候,会开启6-8个连接,而 HTTP2
    只会开启一个连接,这样就减少握手带来的延迟。

  • 头部压缩:HTTP2.0 通过 HPACK
    格式来压缩头部,使用了哈夫曼编码压缩、索引表来对头部大小做优化。索引表是把字符串和数字之间做一个匹配,比如method: GET对应索引表中的2,那么如果之前发送过这个值是,就会缓存起来,之后使用时发现之前发送过该Header字段,并且值相同,就会沿用之前的索引来指代那个Header值。具体实验数据可以参考这里:HTTP/2
    头部压缩技术介绍

图片 10

  • Server
    Push:就是服务端可以主动推送一些东西给客户端,也被称为缓存推送。推送的资源可以备客户端日后之需,需要的时候直接拿出来用,提升了速率。具体的实验可以参考这里:iOS
    HTTP/2 Server Push 探索

图片 11

除了上面讲到的特性,HTTP2.0
还有流量控制、流优先级和依赖性等功能。更多细节可以参考:Hypertext
Transfer Protocol Version 2
(HTTP/2)

相关文章