88bf必发娱乐 20

谈谈 HTTP【88bf必发娱乐】/2 的协议协商机制

关于作者:JerryQu

88bf必发娱乐 1

专注 Web 开发,关注 Web
性能优化与安全。https://imququ.com
个人主页
·
我的文章
·
2
·
  

88bf必发娱乐 2

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

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

任选一种支付方式

88bf必发娱乐 3
88bf必发娱乐 4

1 赞 1 收藏
评论

88bf必发娱乐 5

小结

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

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
数据,不走协商。

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


打赏作者

分享之前我还是要推荐下我自己建的iOS开发学习群:680565220,群里都是学ios开发的,如果你正在学习ios
,小编欢迎你加入,今天分享的这个案例已经上传到群文件,大家都是软件开发党,不定期分享干货(只有iOS软件开发相关的),包括我自己整理的一份2017最新的iOS进阶资料和高级开发教程,欢迎进阶中和进想深入iOS的小伙伴。

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
抓包(两张图可以对照来看):

88bf必发娱乐 6

88bf必发娱乐 7

根据 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
的浏览器却无法升级,只能使用降级方案。

  • (NSString*)getHTTPVersion {NSURLResponse*response
    =self;NSString*version;NSString*funName
    =@”CFURLResponseGetHTTPResponse”; MYURLResponseGetHTTPResponse
    originURLResponseGetHTTPResponse = dlsym(RTLD_DEFAULT, [funName
    UTF8String]); SEL theSelector
    =NSSelectorFromString(@”_CFURLResponse”);if([response
    respondsToSelector:theSelector] &&NULL!=
    originURLResponseGetHTTPResponse) {CFTypeRefcfResponse
    =CFBridgingRetain([response performSelector:theSelector]);if(NULL!=
    cfResponse) {CFHTTPMessageRefmessage =
    originURLResponseGetHTTPResponse(cfResponse);CFStringRefcfVersion
    =CFHTTPMessageCopyVersion;if(NULL!= cfVersion) { version =
    (__bridgeNSString*)cfVersion;CFRelease(cfVersion);
    }CFRelease(cfResponse); } }if(nil== version ||0== version.length) {
    version =@”获取失败”; }returnversion; }@end

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,应用层协议协商)。二者的目标和实现原理基本一致,这里只介绍后者。如图:

88bf必发娱乐 8

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

88bf必发娱乐 9

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

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

HTTP 2.0的协商机制

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

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

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

文章目录

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

88bf必发娱乐 10

NPN 的协商结果是在 Change Cipher Spec 之后加密发送给服务端;而 ALPN
的协商结果是通过 Server Hello 明文发给客户端

Chrome 上的一个插件,HTTP/2 and SPDY indicator会在你访问 http2.0
的网页的时候,以小闪电的形式进行指示

欢迎大家与我交流,分享新鲜的技术~

HTTP 1.1

88bf必发娱乐 11

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

HTTP2.0

使用 NSURLSession 代替 NSURLConnection

这个是h2的协商过程,对于刚才提到的 h2c 的协商过程,与此不同,h2c
利用的是HTTP Upgrade 机制,客户端会发送一个 http
1.1的请求到服务端,这个请求中包含了 http2的升级字段,例如:

88bf必发娱乐 12

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

iOS 客户端接入HTTP 2.0

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

当然,不支持的话,服务器会返回一个不包含 Upgrade 的报头字段的响应。

下面通过 WireShark 来进行调试,接入真机,然后终端输入

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

可以看到客户端在 Client hello 中列出了自己支持的各种应用层协议,比如
spdy3、h2。那么接着看 Server hello 是如何回复的

88bf必发娱乐 13

点击小闪电,会进入一个页面,列举了当前浏览器访问的全部
http2.0的请求,所以,你可以把你想要测试的客户端接口在浏览器访问,然后在这个页面验证下是否支持
http2.0

客户端是使用 h2c 还是 h2,它们可以说是 HTTP2.0的两个版本,h2 是使用 TLS
的HTTP2.0协议,h2c是运行在明文 TCP 协议上的
HTTP2.0协议。浏览器目前只支持h2,也就是说必须基于HTTPS部署,但是客户端可以不部署HTTPS,因为我司早已部署HTTPS,所以我这里的实践都是基于h2的

再者简单粗暴,直接在 iOS 代码中打印,_CFURLResponse 中包含了
httpversion,获取方法就是基于 CFNetwork 相关的 API
来做,这里直接丢出关键代码,完整代码可以参考getHTTPVersion

HTTP/1.1101Switching Protocols Connection:Upgrade Upgrade:h2c [
HTTP/2connection …

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

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

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

保证服务端支持 HTTP2.0,并且留意下 NPN 或 ALPN

进入之后,在手机上访问页面会有源源不断的请求显示在 WireShark
的界面上,数据太多而不利于我们针对性调试,你可以过滤下域名,只关注你想测试的
ip 地址,比如: ip.addr==111.89.211.191 ,当然你的 ip 要支持
HTTP2.0才会有预想的效果哦

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

88bf必发娱乐 14

上面说了一堆名次,什么NPN、ALPN呀,还有h2、h2c之类的,有点懵逼。NPN(Next
Protocol Negotiation)是一个 TLS 扩展,由 Google 在开发 SPDY
协议时提出。随着 SPDY 被 HTTP/2 取代,NPN 也被修订为 ALPN(Application
Layer Protocol
Negotiation,应用层协议协商)。二者目标一致,但实现细节不一样,相互不兼容。以下是它们主要差别:

88bf必发娱乐 15

客户端系统版本 iOS 9 +

88bf必发娱乐 16

charles:这个大家应该都用过,4.0 以上的新版本对
HTTP2.0做了支持,为了方便,你也可以在 charles
上进行调试,但是我发现好像存在 http2.0的一些 bug,目前还没搞清楚什么原因

我的客户端支持了吗?

88bf必发娱乐 17

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

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

88bf必发娱乐 18

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

使用 nghttp2 来调试,这是一个 C 语言实现的
HTTP2.0的库,具体使用方法可以参考:使用 nghttp2 调试 HTTP/2 流量

下面,就开始通过查看 TLS 握手的过程分析HTTP2.0 的协商过程,刚才也说道
ALPN 协商结果是在 Client hello 和 Server hello
中显示的,那就先来看一下Client hello

服务端会根据 client hello
中的协议列表,发过去自己支持的网络协议,假如服务端支持
h2,则直接返回h2,协商成功,如果不支持
h2,则返回一个其他支持的协议,比如HTTP1.1、spdy3

iOS 如何接入 HTTP 2.0呢?其实很简单:

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

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

同时,目前很多地方开始停止对NPN的支持,仅支持
ALPN,所以公司使用的话,最佳是直接使用 ALPN。

rvictl -s 设备 UDID来创建一个映射到 iPhone 的虚拟网卡,UUID 可以在
iTunes 中获取到,运行命令后会看到成功创建 rvi0 虚拟网卡的,双击 rvi0
开始调试。

一切准备就绪之后,也是时候对结果进行验证了,除了刚才提到的 WireShark
之外,你还可以使用下面的几个工具来对 HTTP 2.0 进行测试

服务端收到这个请求后,如果支持 Upgrade 中 列举的协议,这里是
h2c,就会返回支持的响应:

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

88bf必发娱乐 19

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

GET /default.htmHTTP/1.1Host: server.example.comConnection: Upgrade,
HTTP2-Settings Upgrade: h2c HTTP2-Settings:

88bf必发娱乐 20

#import”NSURLResponse+Help.h”#import@implementationNSURLResponsetypedefCFHTTPMessageRef(*MYURLResponseGetHTTPResponse)(CFURLRefresponse);

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

NPN 是服务端发送所支持的 HTTP 协议列表,由客户端选择;而 ALPN
是客户端发送所支持的 HTTP 协议列表,由服务端选择;

消息:消息由帧组成

下面就直接来看看 ALPN 的协商过程是怎样的,ALPN 作为 TLS
的一个扩展,其过程可以通过 WireShark 查看 TLS握手过程来查看