marp | title | theme | paginate | _paginate |
---|---|---|---|---|
true |
HTTP/2 简介 |
uncover |
true |
false |
- HTTP简史
- HTTP/1.1的主要特性和问题
- HTTP/2 的核心概念、主要特性
- HTTP/2 的升级与发现
- HTTP/2 的问题及展望
HTTP
(HyperText Transfer Protocol,超文本传输协议)是互联网上最普遍采用的一种应用协议- 由欧洲核子研究委员会
CERN
的英国工程师Tim Berners-Lee在1991年发明 - Tim Berners-Lee也是WWW的发明者
HTTP/0.9
:只有一行的协议- 请求只有一行,包括
GET
方法和要请求的文档的路径 - 响应是一个超文本文档,没有首部,也没有其他元数据,只有
HTML
- 服务器与客户端之间的连接在每次请求之后都会关闭
- 请求只有一行,包括
HTTP/0.9
的设计目标传递超文本文档
HTTP/0.9
演示
$> 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.
- 1996年
HTTP
工作组发布了RFC 1945
,这就是HTTP/1.0
- 提供请求和响应的各种元数据
- 不局限于超文本的传输,响应可以是任何类型:
HTML
文件、图片、音频等 - 支持内容协商、内容编码、字符集、认证、缓存等
- 从超文本到超媒体传输
HTTP/1.0
演示
$> 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.
- 1997年1月定义
HTTP/1.1
标准的RFC 2068
发布 - 1999年6月
RFC 2616
发布,取代了RFC 2068
- 性能优化
- 持久连接
- 除非明确告知,默认使用持久连接
- 分块编码传输
- 请求管道,支持并行请求处理(应用的非常有限)
- 增强的缓存机制
- 持久连接
HTTP/1.1
演示
>$ telnet www.baidu.com 80
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>
- Google在2009年发布了实验性协议
SPDY
,主要目标是解决HTTP/1.1
的性能限制 - Google工程师在09年11月分享了实验结果 A 2x Faster Web
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
的基础上制定官方标准
- 2015年正式发布
HTTP/2
- 主要目标:改进传输性能,降低延迟,提高吞吐量
- 保持原有的高层协议语义不变
- 根据W3Techs的报告,截止2019年11月,全球已经有 42.1% 的网站开启了
HTTP/2
- Google在2012年设计开发了QUIC协议,让
HTTP
不再基于TCP
- 2018年底,
HTTP/3
标准发布 HTTP/3
协议业务逻辑变化不大,可以简单理解为HTTP/2
+QUIC
- 非持久
HTTP
连接的固定时间成本- 至少两次网络往返: 握手、请求和响应
- 服务处理速度越快,固定延迟的影响就越大
- 持久连接避免
TCP
连接时的三次握手,消除TCP
的慢启动
- 多次请求必须满足先进先出(FIFO)的顺序
- 尽早发送请求,不被每次响应阻塞
<style scoped> li { font-size: 37px; } </style>
HTTP/1.1
的局限性- 只能严格串行地返回响应,不允许一个连接上的多个响应交错到达
- 管道的问题
- 并行处理请求时,服务器必须缓冲管道中的响应,占用服务器资源
- 由于失败可能导致重复处理,非幂等的方法不能pipeline化
- 由于中间代理的兼容性,可能会破坏管道
- 管道的应用非常有限
- 每个
HTTP
请求都会携带500~800字节的header
- 如果使用了
cookie
,每个HTTP
请求会增加几千字节的协议开销 HTTP header
以纯文本形式发送,不会进行任何压缩- 某些时候
HTTP header
开销会超过实际传输的数据一个数量级- 例如访问
RESTful API
时返回JSON
格式的响应
- 例如访问
- 由于
HTTP/1.1
不支持多路复用- 浏览器支持每个主机打开多个连接(例如Chrome是6个)
- 应用使用多域名,将资源分散到多个子域名
- 浏览器连接限制针对的是主机名,不是
IP
地址
- 浏览器连接限制针对的是主机名,不是
- 缺点
- 消耗客户端和服务器资源
- 域名分区增加了额外的
DNS
查询 - 避免不了
TCP
的慢启动
<style scoped> li { font-size: 25px; } h3 { font-size: 40px; } </style>
- 减少请求次数
- 把多个
JavaScript
或CSS
组合为一个文件 - 把多张图片组合为一个更大的复合的图片
- inlining内联,将图片嵌入到
CSS
或者HTML
文件中,减少网络请求次数
- 把多个
增加应用的复杂度,导致缓存、更新等问题,只是权宜之计
- 性能优化
- 支持请求与响应的多路复用
- 支持请求优先级和流量控制
- 支持服务器端推送
- 压缩
HTTP header
降低协议开销
- HTTP的语义不变
HTTP
方法、header
、状态码、URI
- 引入新的二进制分帧数据层
- 将传输的信息分割为消息和帧,并采用二进制格式的编码
- 流(Stream)
- 已建立的连接上的双向字节流
- 该字节流可以携带一个或多个消息
- 消息(Message)
- 与请求/响应消息对应的一系列完整的数据帧
- 帧(Frame)
- 通信的最小单位
- 每个帧包含帧首部,标识出当前帧所属的流
- 所有
HTTP/2
通信都在一个TCP连接上完成 流
是连接中的一个虚拟信道,可以承载双向的消息- 一个连接可以承载任意数量的
流
,每个流
都有一个唯一的整数标识符(1、2...N) 消息
是指逻辑上的HTTP
消息,比如请求、响应等消息
由一或多个帧
组成,这些帧可以交错发送,然后根据每个帧首部的流标识符重新组装
HTTP/1.x
中,如果客户端想发送多个并行的请求,那么必须使用多个TCP
连接HTTP/2
中,客户端可以使用多个流发送请求,同时HTTP
消息被分解为互不依赖的帧,交错传输,最后在另一端重新组装
- 详细说明请参考HTTP/2规范
<style scoped> li { font-size: 30px; } table { font-size: 25px; } </style>
- 客户端通过
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
允许每个流关联一个31bit
的优先值0
最高优先级2^31 -1
最低优先级
- 浏览器会基于资源的类型、在页面中的位置等因素,决定请求的优先次序
- 服务器可以根据流的优先级,控制资源分配,优先将高优先级的帧发送给客户端
HTTP/2
没有规定具体的优先级算法
- 流量控制有方向性,即接收方可能根据自己的情况为每个
流
,乃至整个连接设置任意窗口大小 - 连接建立后,客户端与服务器交换
SETTINGS
帧,设置 双向的流量控制窗口大小 - 流量控制窗口大小通过
WINDOW_UPDATE
帧更新 HTTP/2
流量控制和TCP
流量控制的机制相同,但TCP
流量控制不能对同一个连接内的多个流
实施差异化策略
<style scoped> li { font-size: 33px; } </style>
- 服务器可以对一个客户端请求发送多个响应
- 服务器通过发送
PUSH_PROMISE
帧来发起推送流 - 客户端可以使用
HTTP header
向服务器发送信号,列出它希望推送的资源 - 服务器可以智能分析客户端的需求,自动推送关键资源
HTTP/2
使用HPACK压缩格式压缩请求/响应头- 通过静态霍夫曼码对发送的
header
字段进行编码,减小了它们的传输大小 - 客户端和服务器使用
索引表
来维护和更新header
字段。对于相同的数据,不再重复发送
- 通过静态霍夫曼码对发送的
HTTP/1.x
还将长期存在,客户端和服务器必须同时支持1.x
和2.0
- 客户端和服务器在开始交换数据前,必须发现和协商使用哪个版本的协议进行通信
HTTP/2
定义了两种协商机制- 通过安全连接
TLS
和ALPN
进行协商 - 基于
TCP
连接的协商机制
- 通过安全连接
HTTP/2
标准不要求必须基于TLS
,但浏览器要求必须基于TLS
- Web上存在大量的代理和中间设备:缓存服务器、安全网关、加速器等等
- 如果任何中间设备不支持,连接都不会成功
- 建立
TLS
信道,端到端加密传输,绕过中间代理,实现可靠的部署 - 新协议一般都要依赖于建立
TLS
信道,例如WebSocket
、SPDY
- 基于
TLS
运行的HTTP/2
被称为h2
- 直接在
TCP
之上运行的HTTP/2
被称为h2c
- 客户端测试工具
curl
(> 7.46.0) - 服务器端
Tomcat 9.x
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" >
<UpgradeProtocol
className="org.apache.coyote.http2.Http2Protocol"/>
</Connector>
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
- Magic帧
- ASCII 编码,12字节
- 何时发送?
- 接收到服务器发送来的 101 Switching Protocols后
- TLS 握手成功后
- Preface 内容
- 交换
settings
帧(client -> server)
- 交换
settings
帧(server -> client)
settings
ACK 帧 (client <-> server)
- 保密性
- 完整性
- 身份验证
- 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
信道时使用,之后的通信使用握手时生成的共享密钥加密
<style scoped> li { font-size: 30px; } </style>
- 密钥交换算法
- 双方在完全没有对方任何预先信息,通过不安全信道创建密钥
- 1976年,Diffie–Hellman key exchange,简称 DH
- 基于椭圆曲线(Elliptic Curve)升级DH协议,ECDHE
- 身份验证算法
- 非对称加密算法,Public Key Infrastructure(PKI)
- 对称加密算法、强度、工作模式
- 工作模式:将明文分成多个等长的
Block
模块,对每个模块分别加解密
- 工作模式:将明文分成多个等长的
- hash签名算法
-
基于
TLS
运行的HTTP/2
使用ALPN
扩展做协议协商- 客户端在
ClientHello
消息中增加ProtocolNameList
字段,包含自己支持的应用协议 - 服务器检查
ProtocolNameList
字段,在ServerHello
消息中以ProtocolName
字段返回选中的协议
- 客户端在
-
在
TLS
握手的同时协商应用协议,省掉了HTTP
的Upgrade
机制所需的额外往返时间
- 客户端:浏览器
- 服务器端:
Tomcat 9.x
Tomcat
提供了三种不同的TLS
实现- Java运行时提供的
JSSE
实现 - 使用了
OpenSSL
的JSSE
实现 APR
实现,默认情况下使用OpenSSL
引擎
- Java运行时提供的
<style scoped> li { font-size: 35px; } </style>
- JSSE
- 非常慢
- ALPN是因为
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>
OpenSSL
性能比纯Java实现好很多;使用TLS
可以不再需要APR
Linux
上NIO.2
是通过epoll
来模拟实现的EPollPort.java
- 生成private key和自签名证书
keytool -genkey -alias tomcat -keyalg RSA
- 配置
server.xml
<Connector
protocol="org.apache.coyote.http11.Http11NioProtocol"
port="8443" maxThreads="200"
sslImplementationName=
"org.apache.tomcat.util.net.jsse.JSSEImplementation"
scheme="https" secure="true" SSLEnabled="true"
keystoreFile="${user.home}/.keystore" keystorePass="changeit"
clientAuth="false" sslProtocol="TLS">
<UpgradeProtocol
className="org.apache.coyote.http2.Http2Protocol" />
</Connector>
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
- 安装
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>
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"]
- ClientHello
- ServerHello
TCP
及TCP+TLS
建立连接需要多次round trips
- Quick UDP Internet Connections
- 由Goolge开发,并已经在Google部署使用
QUIC: next generation multiplexed transport over UDP