View on GitHub

blog

Stay hungry. Stay foolish.

HTTP/2 简介


Outline

回顾HTTP的发展简史,理解HTTP在设计上的关键转变,以及每次转变的动机

HTTP简史


HTTP简史


HTTP简史

$> telnet apache.org 80

Trying 95.216.24.32...
Connected to apache.org.
Escape character is '^]'.

GET /foundation/

<!DOCTYPE html>
...
Connection closed by foreign host.

HTTP简史


HTTP简史

$> telnet apache.org 80
Trying 95.216.24.32...
Connected to apache.org.
GET /foundation/ HTTP/1.0
Accept: */*

HTTP/1.1 200 OK
Server: Apache/2.4.18 (Ubuntu)
Content-Length: 46012
Connection: close
Content-Type: text/html

<!DOCTYPE html>
...
Connection closed by foreign host.

HTTP简史


HTTP简史

Trying 14.215.177.38… Connected to www.a.shifen.com.

GET /s?wd=http2 HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 Connection: keep-alive Host: www.baidu.com

HTTP/1.1 200 OK Connection: Keep-Alive Content-Type: text/html;charset=utf-8


---

Date: Sun, 06 Oct 2019 12:49:28 GMT Server: BWS/1.1 Transfer-Encoding: chunked

ffa <!DOCTYPE html> …

1be7 …

0

GET /img/bd_logo1.png HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 Connection: close Host: www.baidu.com

HTTP/1.1 200 OK


---

Content-Length: 7877 Content-Type: image/png Date: Sun, 06 Oct 2019 13:05:06 GMT Etag: “1ec5-502264e2ae4c0” Expires: Wed, 03 Oct 2029 13:05:06 GMT Last-Modified: Wed, 03 Sep 2014 10:00:27 GMT Server: Apache Set-Cookie: BAIDUID=0D01C3C9C00A6019C16F79CAEB1EFE91:FG=1 Connection: close

. . . . . .

Connection closed by foreign host.


---
<style scoped>
li {
  font-size: 35px;
}
p {
  font-size: 30px;
}
</style>
### HTTP简史
* Google在2009年发布了实验性协议`SPDY`,主要目标是解决`HTTP/1.1`的性能限制
* Google工程师在09年11月分享了实验结果 [A 2x Faster Web](https://blog.chromium.org/2009/11/2x-faster-web.html)
> So far we have only tested SPDY in lab conditions. The initial results are very encouraging: when we download the top 25 websites over simulated home network connections, we see a significant improvement in performance - pages loaded up to 55% faster. 
* 2012年,`SPDY`得到Chrome、Firefox和Opera的支持
* `HTTP-WG`(HTTP Working Group)开始在`SPDY`的基础上制定官方标准


---
### HTTP简史
* 2015年正式发布`HTTP/2`
  * 主要目标:改进传输性能,降低延迟,提高吞吐量
  * 保持原有的高层协议语义不变
* 根据[W3Techs的报告](https://w3techs.com/technologies/details/ce-http2/all/all),截止2019年11月,全球已经有 **42.1%** 的网站开启了`HTTP/2`

---
### HTTP简史
* Google在2012年设计开发了[QUIC协议](https://en.wikipedia.org/wiki/QUIC),让`HTTP`不再基于`TCP`
* 2018年底,`HTTP/3`标准发布
* `HTTP/3`协议业务逻辑变化不大,可以简单理解为 `HTTP/2` + `QUIC`

---
### HTTP/1.1 持久连接
![tcp-connection1 w:850](/blog/docs/media/http2/tcp-connection1.png)

---
### HTTP/1.1 持久连接
![tcp-connection1 w:850](/blog/docs/media/http2/tcp-connection2.png)

---
### HTTP/1.1 持久连接
* 非持久`HTTP`连接的固定时间成本
  * 至少两次网络往返: 握手、请求和响应
* 服务处理速度越快,固定延迟的影响就越大
* 持久连接避免`TCP`连接时的三次握手,消除`TCP`的慢启动

---
### HTTP/1.1 管道
* 多次请求必须满足**先进先出**(FIFO)的顺序

![keep-alive w:600](/blog/docs/media/http2/http-keep-alive.png)

---
### HTTP/1.1 管道
* 尽早发送请求,不被每次响应阻塞

![keep-alive w:750](/blog/docs/media/http2/http-pipeline.png)

---
<style scoped>
li {
  font-size: 37px;
}
</style>
### HTTP/1.1 管道

* `HTTP/1.1`的局限性
  * 只能严格串行地返回响应,不允许一个连接上的多个响应交错到达
* 管道的问题
  * 并行处理请求时,服务器必须缓冲管道中的响应,占用服务器资源
  * 由于失败可能导致重复处理,非幂等的方法不能pipeline化
  * 由于中间代理的兼容性,可能会破坏管道
* 管道的应用非常有限

---
### `HTTP/1.1` 的协议开销
* 每个`HTTP`请求都会携带500~800字节的`header`
* 如果使用了`cookie`,每个`HTTP`请求会增加几千字节的协议开销
* `HTTP header`以纯文本形式发送,不会进行任何压缩
* 某些时候`HTTP header`开销会超过实际传输的数据一个数量级
  * 例如访问`RESTful API`时返回`JSON`格式的响应

---
### `HTTP/1.1`性能优化建议
* 由于`HTTP/1.1`不支持多路复用
  * 浏览器支持每个主机打开多个连接(例如Chrome是6个)
  * 应用使用多域名,将资源分散到多个子域名
    * 浏览器连接限制针对的是主机名,不是`IP`地址
* 缺点
  * 消耗客户端和服务器资源
  * 域名分区增加了额外的`DNS`查询
  * 避免不了`TCP`的慢启动

---
<style scoped>
li {
  font-size: 25px;
}
h3 {
  font-size: 40px;
}
</style>
### `HTTP/1.1`性能优化建议
* 使用多域名分区
![connection-view w:800](/blog/docs/media/http2/connection-view.png)

---
### `HTTP/1.1`性能优化建议
* 减少请求次数
  * 把多个`JavaScript`或`CSS`组合为一个文件
  * 把多张图片组合为一个更大的复合的图片
  * inlining内联,将图片嵌入到`CSS`或者`HTML`文件中,减少网络请求次数

*增加应用的复杂度,导致缓存、更新等问题,只是权宜之计*

---
### `HTTP/2` 的目标
* 性能优化
  * 支持请求与响应的多路复用
  * 支持请求优先级和流量控制
  * 支持服务器端推送
  * 压缩`HTTP header`降低协议开销
* HTTP的语义不变
  * `HTTP`方法、`header`、状态码、`URI`

---
### `HTTP/2` 二进制分帧层
* 引入新的二进制分帧数据层
* 将传输的信息分割为消息和帧,并采用二进制格式的编码

![binary-framing-layer w:800](/blog/docs/media/http2/binary-framing-layer.png)

---
### `HTTP/2` 的核心概念
* 流(Stream)
  * 已建立的连接上的双向字节流
  * 该字节流可以携带一个或多个消息
* 消息(Message)
  * 与请求/响应消息对应的一系列完整的数据帧
* 帧(Frame)
  * 通信的最小单位
  * 每个帧包含帧首部,标识出当前帧所属的流

---
### `HTTP/2` 的核心概念

![stream-message-frame w:750](/blog/docs/media/http2/stream-message-frame.png)

---
### `HTTP/2` 的核心概念
* 所有`HTTP/2`通信都在一个TCP连接上完成
* `流`是连接中的一个虚拟信道,可以承载双向的消息
* 一个连接可以承载任意数量的`流`,每个`流`都有一个唯一的整数标识符(1、2...N)
* `消息`是指逻辑上的`HTTP`消息,比如请求、响应等
* `消息`由一或多个`帧`组成,这些帧可以交错发送,然后根据每个帧首部的**流标识符**重新组装

---
### `HTTP/2`请求与响应的多路复用
* `HTTP/1.x`中,如果客户端想发送多个并行的请求,那么必须使用多个`TCP`连接
* `HTTP/2`中,客户端可以使用多个流发送请求,同时`HTTP`消息被分解为互不依赖的帧,交错传输,最后在另一端重新组装

![http2-connection w:800](/blog/docs/media/http2/http2-connection.png)

---
### `HTTP/2` 帧格式
![frame format](/blog/docs/media/http2/frame-format.png)

* 详细说明请参考[HTTP/2规范](https://tools.ietf.org/html/rfc7540)

---
<style scoped>
li {
  font-size: 30px;
}
table {
  font-size: 25px;
}
</style>
### `HTTP/2` 帧类型
* 客户端通过`HEADERS`帧来发起新的`流`
* 服务器通过`PUSH_PROMISE`帧来发起推送流

| 帧类型        | 类型编码 | 用途                         |
| ------------- | -------- | ---------------------------- |
| DATA          | 0x0      | 传输HTTP消息体               |
| HEADERS       | 0x1      | 传输HTTP头部                 |
| PRIORITY      | 0x2      | 指定流的优先级               |
| RST_STREAM    | 0x3      | 通知流的非正常               |
| SETTINGS      | 0x4      | 修改连接或者流的配置         |
| PUSH_PROMISE  | 0x5      | 服务端推送资源时的请求帧     |
| PING          | 0x6      | 心跳检测,计算`RTT`往返时间  |
| GOAWAY        | 0x7      | 优雅的终止连接,或者通知错误 |
| WINDOW_UPDATE | 0x8      | 针对流或者连接,实现流量控制 |
| CONTINUATION  | 0x9      | 传递较大HTTP头部时的持续帧   |


---
### `HTTP/2` 请求优先级
* `HTTP/2`允许每个流关联一个31`bit`的优先值
  * `0` 最高优先级
  * `2^31 -1` 最低优先级
* 浏览器会基于资源的类型、在页面中的位置等因素,决定请求的优先次序
* 服务器可以根据流的优先级,控制资源分配,优先将高优先级的帧发送给客户端
* `HTTP/2`没有规定具体的优先级算法

---
### `HTTP/2` 流量控制
* 流量控制有方向性,即接收方可能根据自己的情况为每个`流`,乃至整个连接设置任意窗口大小
* 连接建立后,客户端与服务器交换`SETTINGS`帧,设置 双向的流量控制窗口大小
* 流量控制窗口大小通过`WINDOW_UPDATE`帧更新
* `HTTP/2`流量控制和`TCP`流量控制的机制相同,但`TCP`流量控制不能对同一个连接内的多个`流`实施差异化策略

---
<style scoped>
li {
  font-size: 33px;
}
</style>
### `HTTP/2` 服务器端推送
* 服务器可以对一个客户端请求发送多个响应
* 服务器通过发送`PUSH_PROMISE`帧来发起推送流
* 客户端可以使用`HTTP header`向服务器发送信号,列出它希望推送的资源
* 服务器可以智能分析客户端的需求,自动推送关键资源

![server-push w:800](/blog/docs/media/http2/server-push.png)

---
### `HTTP header`压缩
* `HTTP/2`使用[HPACK](https://tools.ietf.org/html/rfc7541)压缩格式压缩请求/响应头
  * 通过静态霍夫曼码对发送的`header`字段进行编码,减小了它们的传输大小
  * 客户端和服务器使用`索引表`来维护和更新`header`字段。对于相同的数据,不再重复发送

---
### `HTTP header`压缩
![http header w:700](/blog/docs/media/http2/http-header.png)

---
### `HTTP/2` vs `HTTP/1.1`
* https://http2.akamai.com/demo
![http2 vs http1 w:700](/blog/docs/media/http2/http2-vs-http1.gif)

---
### `HTTP/2`的升级与发现
* `HTTP/1.x`还将长期存在,客户端和服务器必须同时支持`1.x`和`2.0`
* 客户端和服务器在开始交换数据前,必须发现和协商使用哪个版本的协议进行通信
* `HTTP/2`定义了两种协商机制
  * 通过安全连接`TLS`和`ALPN`进行协商
  * 基于`TCP`连接的协商机制

---
### `HTTP/2`的升级与发现
* `HTTP/2`标准不要求必须基于`TLS`,但浏览器要求必须基于`TLS`
  * Web上存在大量的代理和中间设备:缓存服务器、安全网关、加速器等等
  * 如果任何中间设备不支持,连接都不会成功
  * 建立`TLS`信道,端到端加密传输,绕过中间代理,实现可靠的部署
  * 新协议一般都要依赖于建立`TLS`信道,例如`WebSocket`、`SPDY`

---
### `h2`和`h2c`升级协商机制
* 基于`TLS`运行的`HTTP/2`被称为`h2`
* 直接在`TCP`之上运行的`HTTP/2`被称为`h2c`

![h2-h2c width:800](/blog/docs/media/http2/h2-h2c.png)

--- 
### `h2c`演示环境
* 客户端测试工具 `curl` (> 7.46.0)
* 服务器端 `Tomcat 9.x`


---
### `h2c`协议升级
* `curl http://localhost:8080 --http2 -v`

GET / HTTP/1.1 Host: localhost:8080 User-Agent: curl/7.64.1 Accept: / Connection: Upgrade, HTTP2-Settings Upgrade: h2c HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA

< HTTP/1.1 101 < Connection: Upgrade < Upgrade: h2c


---
### `HTTP/2`连接建立
![start-http2-connection width:550](/blog/docs/media/http2/start-http2-connection.png)

---
### `HTTP/2`连接建立
* Magic帧
  * ASCII 编码,12字节
  * 何时发送?
    * 接收到服务器发送来的 101 Switching Protocols后
    * TLS 握手成功后
  * Preface 内容

![magic-frame](/blog/docs/media/http2/magic-frame.png)

---
### `HTTP/2`连接建立
* 交换`settings`帧(client -> server)

![setting-frame](/blog/docs/media/http2/setting-frame1.png)

---
### `HTTP/2`连接建立
* 交换`settings`帧(server -> client)

![setting-frame](/blog/docs/media/http2/setting-frame2.png)

---
### `HTTP/2`连接建立
* `settings` ACK 帧 (client <-> server)

![setting-frame](/blog/docs/media/http2/setting-frame3.png)

---
### `TLS`协议的设计目标
* 保密性
* 完整性
* 身份验证

---
### `TLS`发展史
* 1994年,NetScape 设计了`SSL`协议(Secure Sockets Layer) 1.0,未正式发布
* 1995年,NetScape 发布 `SSL` 2.0
* 1996年,发布`SSL` 3.0
* 1999年,IETF标准化了`SSL`协议,更名为`TLS`(Transport Layer Security),发布`TLS` 1.0
* 2006年4月,IETF 工作组发布了`TLS` 1.1
* 2008年8月,IETF 工作组发布了`TLS` 1.2
* 2018年8月,`TLS` 1.3正式发布

---
### `TLS` 1.2 握手过程
![bg right w:600](/blog/docs/media/http2/tls-handshake.png)
* 验证身份
* 达成安全套件共识
* 传递密钥
* 加密通讯
非对称加密只在建立`TLS`信道时使用,之后的通信使用握手时生成的共享密钥加密

---
<style scoped>
li {
  font-size: 30px;
}
</style>
### `TLS` 安全密码套件
![cipher-suites width:800](/blog/docs/media/http2/cipher-suites.png)
* 密钥交换算法
  * 双方在完全没有对方任何预先信息,通过不安全信道创建密钥
  * 1976年,Diffie–Hellman key exchange,简称 DH
  * 基于椭圆曲线(Elliptic Curve)升级DH协议,ECDHE
* 身份验证算法
  * 非对称加密算法,Public Key Infrastructure(PKI)
* 对称加密算法、强度、工作模式
  * 工作模式:将明文分成多个等长的`Block`模块,对每个模块分别加解密
* hash签名算法

---
### `TLS`1.3的握手优化
* [An Overview of TLS 1.3 – Faster and More Secure](https://www.thesslstore.com/blog/explaining-ssl-handshake/)

![tls-1.3-handshake-performance width:700](/blog/docs/media/http2/tls-1.3-handshake-performance.png)

---
### 测试`TLS`的支持情况
* https://www.ssllabs.com/ssltest/index.html

![tlstest width:850](/blog/docs/media/http2/tlstest.png)

---
### Application-Layer Protocol Negotiation
* 基于`TLS`运行的`HTTP/2`使用`ALPN`扩展做协议协商
  * 客户端在`ClientHello`消息中增加`ProtocolNameList`字段,包含自己支持的应用协议
  * 服务器检查`ProtocolNameList`字段,在`ServerHello`消息中以`ProtocolName`字段返回选中的协议

* 在`TLS`握手的同时协商应用协议,省掉了`HTTP`的`Upgrade`机制所需的额外往返时间

---
### ALPN

![alpn width:1200](/blog/docs/media/http2/alpn.png)

---
### `h2`演示环境
* 客户端:浏览器
* 服务器端:`Tomcat 9.x`
  * `Tomcat`提供了三种不同的`TLS`实现
    * Java运行时提供的`JSSE`实现
    * 使用了`OpenSSL`的`JSSE`实现
    * `APR`实现,默认情况下使用`OpenSSL`引擎

---
<style scoped>
li {
  font-size: 35px;
}
</style>
### `Tomcat`三种`TLS`实现
* JSSE
  * 非常慢
  * [ALPN](https://tools.ietf.org/html/rfc7301)是因为`HTTP/2`才在2014年出现,JDK8不支持`ALPN`
* `OpenSSL`实现
  * 只使用了`OpenSSL`,没有使用其他本地代码(native socket, poller等)
  * 可以配合 NIO 和 NIO2
* `APR`
  * 大量的native code
  * `TLS`同样使用了`OpenSSL`

---
<style scoped>
li {
  font-size: 30px;
}
h3 {
  font-size: 45px;
}
</style>
### `TLS`实现的性能对比
* `OpenSSL`性能比纯Java实现好很多;使用`TLS`可以不再需要`APR`
* `Linux`上`NIO.2`是通过`epoll`来模拟实现的[EPollPort.java](https://github.com/openjdk/jdk/blob/6bab0f539fba8fb441697846347597b4a0ade428/src/java.base/linux/classes/sun/nio/ch/EPollPort.java)

![jsse-openssl](/blog/docs/media/http2/jsse-openssl.png)

---
### 使用`JSSE`
* 生成private key和自签名证书
  * `keytool -genkey -alias tomcat -keyalg RSA`
* 配置`server.xml`


---
### 使用`JSSE`

* `JDK8`不支持`ALPN`

严重 [main] org.apache.coyote.http11.AbstractHttp11Protocol.configureUpgradeProtocol The upgrade handler [org.apache.coyote.http2.Http2Protocol] for [h2] only supports upgrade via ALPN but has been configured for the [“https-jsse-nio-8443”] connector that does not support ALPN.


* `JDK11`

信息 [main] org.apache.coyote.http11.AbstractHttp11Protocol.configureUpgradeProtocol The [“https-jsse-nio-8443”] connector has been configured to support negotiation to [h2] via ALPN


---
### 使用`OpenSSL`
* 安装`tomcat-native`
  * `brew install tomcat-native`
* 配置`$CATALINA_HOME/bin/setenv.sh`

CATALINA_OPTS=”$CATALINA_OPTS -Djava.library.path=/usr/local/opt/tomcat-native/lib”

* 配置server.xml

<Connector protocol=”org.apache.coyote.http11.Http11NioProtocol” sslImplementationName= “org.apache.tomcat.util.net.openssl.OpenSSLImplementation” … > </Connector>



---
### 使用`OpenSSL`
* `JDK8` & `JDK11`

信息 [main] org.apache.coyote.http11.AbstractHttp11Protocol.configureUpgradeProtocol The [“https-openssl-nio-8443”] connector has been configured to support negotiation to [h2] via ALPN

信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄 [“https-openssl-nio-8443”] ```


ALPN协议协商

client-hello


ALPN协议协商

bg right width:700


使用Chrome开发者工具观察

chrome width:1000


HTTP/2的问题


HTTP/2的问题

tcp-tls width:800


QUIC

quic width:900


QUIC

QUIC: next generation multiplexed transport over UDP

google-live width:1000


参考资料


bg right

Thank You!