HTTP/2 简介

# Outline

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

  • HTTP简史
  • HTTP/1.1的主要特性和问题
  • HTTP/2 的核心概念、主要特性
  • HTTP/2 的升级与发现
  • HTTP/2 的问题及展望

# HTTP简史

  • HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上最普遍采用的一种应用协议
  • 由欧洲核子研究委员会CERN的英国工程师Tim Berners-Lee在1991年发明
  • Tim Berners-Lee也是WWW的发明者

# HTTP简史

  • HTTP/0.9:只有一行的协议
    • 请求只有一行,包括GET方法和要请求的文档的路径
    • 响应是一个超文本文档,没有首部,也没有其他元数据,只有HTML
    • 服务器与客户端之间的连接在每次请求之后都会关闭
  • HTTP/0.9的设计目标传递超文本文档

# HTTP简史

  • HTTP/0.9演示
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$> 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简史

  • 1996年HTTP工作组发布了RFC 1945,这就是HTTP/1.0
  • 提供请求和响应的各种元数据
  • 不局限于超文本的传输,响应可以是任何类型:HTML文件、图片、音频等
  • 支持内容协商、内容编码、字符集、认证、缓存等
  • 超文本超媒体传输

# HTTP简史

  • HTTP/1.0演示
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$> 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简史

  • 1997年1月定义HTTP/1.1标准的RFC 2068发布
  • 1999年6月RFC 2616发布,取代了RFC 2068
  • 性能优化
    • 持久连接
      • 除非明确告知,默认使用持久连接
    • 分块编码传输
    • 请求管道,支持并行请求处理(应用的非常有限)
    • 增强的缓存机制

# HTTP简史

  • HTTP/1.1演示
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
>$ 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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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.

# HTTP简史

  • 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的基础上制定官方标准

# HTTP简史

  • 2015年正式发布HTTP/2
    • 主要目标:改进传输性能,降低延迟,提高吞吐量
    • 保持原有的高层协议语义不变
  • 根据W3Techs的报告,截止2019年11月,全球已经有 42.1% 的网站开启了HTTP/2

# HTTP简史

  • Google在2012年设计开发了QUIC协议,让HTTP不再基于TCP
  • 2018年底,HTTP/3标准发布
  • HTTP/3协议业务逻辑变化不大,可以简单理解为 HTTP/2 + QUIC

# HTTP/1.1 持久连接

tcp-connection1


# HTTP/1.1 持久连接

tcp-connection2


# HTTP/1.1 持久连接

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

# HTTP/1.1 管道

  • 多次请求必须满足先进先出(FIFO)的顺序

http-keep-alive


# HTTP/1.1 管道

  • 尽早发送请求,不被每次响应阻塞

http-pipeline


# 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的慢启动

# HTTP/1.1性能优化建议

  • 使用多域名分区 connection view

# HTTP/1.1性能优化建议

  • 减少请求次数
    • 把多个JavaScriptCSS组合为一个文件
    • 把多张图片组合为一个更大的复合的图片
    • inlining内联,将图片嵌入到CSS或者HTML文件中,减少网络请求次数

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


# HTTP/2 的目标

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

# HTTP/2 二进制分帧层

  • 引入新的二进制分帧数据层
  • 将传输的信息分割为消息和帧,并采用二进制格式的编码

binary-framing-layer


# HTTP/2 的核心概念

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

# HTTP/2 的核心概念

stream-message-frame


# HTTP/2 的核心概念

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

# HTTP/2请求与响应的多路复用

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

http2-connection


# HTTP/2 帧格式

frame format


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

# HTTP/2 流量控制

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

# HTTP/2 服务器端推送

  • 服务器可以对一个客户端请求发送多个响应
  • 服务器通过发送PUSH_PROMISE帧来发起推送流
  • 客户端可以使用HTTP header向服务器发送信号,列出它希望推送的资源
  • 服务器可以智能分析客户端的需求,自动推送关键资源

server-push.png


# HTTP header压缩

  • HTTP/2使用HPACK压缩格式压缩请求/响应头
    • 通过静态霍夫曼码对发送的header字段进行编码,减小了它们的传输大小
    • 客户端和服务器使用索引表来维护和更新header字段。对于相同的数据,不再重复发送

# HTTP header压缩

http header


# HTTP/2 vs HTTP/1.1


# HTTP/2的升级与发现

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

# HTTP/2的升级与发现

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

# h2h2c升级协商机制

  • 基于TLS运行的HTTP/2被称为h2
  • 直接在TCP之上运行的HTTP/2被称为h2c

h2-h2c


# h2c演示环境

  • 客户端测试工具 curl (> 7.46.0)
  • 服务器端 Tomcat 9.x
1
2
3
4
5
6
<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" >
    <UpgradeProtocol 
            className="org.apache.coyote.http2.Http2Protocol"/>
</Connector>

# h2c协议升级

  • curl http://localhost:8080 --http2 -v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
> 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


# HTTP/2连接建立

  • Magic帧
    • ASCII 编码,12字节
    • 何时发送?
      • 接收到服务器发送来的 101 Switching Protocols后
      • TLS 握手成功后
    • Preface 内容

magic-frame


# HTTP/2连接建立

  • 交换settings帧(client -> server)

setting-frame


# HTTP/2连接建立

  • 交换settings帧(server -> client)

setting-frame


# HTTP/2连接建立

  • settings ACK 帧 (client <-> server)

setting-frame


# 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 握手过程

tls-handshake

  • 验证身份
  • 达成安全套件共识
  • 传递密钥
  • 加密通讯 非对称加密只在建立TLS信道时使用,之后的通信使用握手时生成的共享密钥加密

# TLS 安全密码套件

cipher-suites

  • 密钥交换算法
    • 双方在完全没有对方任何预先信息,通过不安全信道创建密钥
    • 1976年,Diffie–Hellman key exchange,简称 DH
    • 基于椭圆曲线(Elliptic Curve)升级DH协议,ECDHE
  • 身份验证算法
    • 非对称加密算法,Public Key Infrastructure(PKI)
  • 对称加密算法、强度、工作模式
    • 工作模式:将明文分成多个等长的Block模块,对每个模块分别加解密
  • hash签名算法

# TLS1.3的握手优化

tls-1.3-handshake-performance


# 测试TLS的支持情况

tlstest width:850


# Application-Layer Protocol Negotiation

  • 基于TLS运行的HTTP/2使用ALPN扩展做协议协商

    • 客户端在ClientHello消息中增加ProtocolNameList字段,包含自己支持的应用协议
    • 服务器检查ProtocolNameList字段,在ServerHello消息中以ProtocolName字段返回选中的协议
  • TLS握手的同时协商应用协议,省掉了HTTPUpgrade机制所需的额外往返时间


# ALPN

alpn


# h2演示环境

  • 客户端:浏览器
  • 服务器端:Tomcat 9.x
    • Tomcat提供了三种不同的TLS实现
      • Java运行时提供的JSSE实现
      • 使用了OpenSSLJSSE实现
      • APR实现,默认情况下使用OpenSSL引擎

# Tomcat三种TLS实现

  • JSSE
    • 非常慢
    • ALPN是因为HTTP/2才在2014年出现,JDK8不支持ALPN
  • OpenSSL实现
    • 只使用了OpenSSL,没有使用其他本地代码(native socket, poller等)
    • 可以配合 NIO 和 NIO2
  • APR
    • 大量的native code
    • TLS同样使用了OpenSSL

# TLS实现的性能对比

  • OpenSSL性能比纯Java实现好很多;使用TLS可以不再需要APR
  • LinuxNIO.2是通过epoll来模拟实现的EPollPort.java

jsse-openssl


# 使用JSSE

  • 生成private key和自签名证书
    • keytool -genkey -alias tomcat -keyalg RSA
  • 配置server.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<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>

# 使用JSSE

  • JDK8不支持ALPN
1
2
3
4
5
严重 [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
1
2
3
4
信息 [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
1
CATALINA_OPTS="$CATALINA_OPTS -Djava.library.path=/usr/local/opt/tomcat-native/lib"
  • 配置server.xml
1
2
3
4
5
6
<Connector
    protocol="org.apache.coyote.http11.Http11NioProtocol"
    sslImplementationName=
        "org.apache.tomcat.util.net.openssl.OpenSSLImplementation"
    ... >
</Connector>

# 使用OpenSSL

  • JDK8 & JDK11
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
信息 [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协议协商

  • ClientHello

client-hello


# ALPN协议协商

ServerHello

  • ServerHello

# 使用Chrome开发者工具观察

chrome width:1000


# HTTP/2的问题

  • HTTP/2消除了HTTP协议的队首阻塞现象,但TCP层面上仍然存在队首阻塞
  • HTTP/2多请求复用一个TCP连接,丢包可能会block住所有的HTTP请求

head-of-line


# HTTP/2的问题

  • TCPTCP+TLS建立连接需要多次round trips tcp-tls

# QUIC

  • Quick UDP Internet Connections
  • 由Goolge开发,并已经在Google部署使用

quic


# QUIC

QUIC: next generation multiplexed transport over UDP

google-live


# 参考资料

comments powered by Disqus