玩转 HTTP3

超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。自1990年代初以来,HTTP 协议是整个Internet进行数据通信的基础。

在 HTTP/1.0 中,每一个TCP请求或响应都会被分配一个新的连接,这就导致了连接启动缓慢,在此之后,如何规避 TCP 启动慢就一直是 HTTP协议改善的核心。

此后,在HTTP/1.1中引入了keep-alive 的概念,其允许在同一个 TCP 连接中对多个请求或响应进行序列号,从而使得不需要为每个请求都设置新的连接,避免建立新连接带来的网络开销。但是这个版本的keep-alive 连接不支持同时发送多个请求,随着互联网的迅猛发展,又带来了新的问题,如何让数据发送效率提高。

在2015年,它HTTP发布了第二个版本,即HTTP/2,该进行了重大更新。例如:

  • 在建立连接后,可以多路复用
  • 在建立连接后,一次的请求与被响应,视为流
  • 数据传输分为二进制帧片段

而 HTTP/3是HTTP协议的第三个主要版本。在HTTP/3中,将弃用TCP协议,改为使用基于UDP协议的QUIC协议实现。实际上 HTTP/3 的前身是HTTP over QUIC,QUIC 是快速UDP网络连接的简称,由Google公司研发,该协议旨在取代TCP协议,使网页传输更快、更稳定、更安全。

2018年10月,互联网工程任务组(IETF) HTTP和QUIC工作组主席Mark Nottingham提出了将HTTP-over-QUIC更名为HTTP/3,以区分其特点以及与Google 公司的QUIC的独立性。

为什么使用UDP+QUIC

实际上,在此前的HTTP 协议中,一直是使用TCP作为传输协议。为什么在HTTP/3中要换成UDP呢?众所周知,TCP 是一种面向连接的、可靠的、基于字节流的传输层通信协议,在数据传输过程中其加入了序列号、对收到的保温进行排序以检测重复数据,数据重传、拥塞控制、使用校验和确保无错传输、流控制等。使用TCP 协议进行数据传输会经过三次握手,当使用TCP创建一个连接。在连接创建过程中,很多参数要被初始化,例如序号被初始化以保证按序传输和连接的强壮性,其目的是保证数传输和断开的可靠,确保所有数据都被完全传输。因此很多对业务稳定性非常高的协议都一直采用TCP协议作为数据传输协议。

TCP 报头

事实上,在HTTP/2 之前的版本中,都是采用TCP 进行数据传输的。而 HTTP/2 引入的多路复用技术改善了HTTP/1.1的keep-alive 带来的缺陷,但是当数据包丢失增加,HTTP/2的性能会由于TCP处理包重传的方式(HOL阻塞)而下降,从而非常影效率。因为所有的流都是共享同一个连接,当数据包丢失超出阈值,HTTP/2的运行效率可能还不如HTTP/1的效率高。

而UDP协议则比较简单,其特点如下:

  • 无需建立连接,因此UDP不会引入建立连接的时延。
  • 没有TCP 那么复杂的报头,例如重传、序列号等等
  • 速度快,但是不保证数据的完整性

其报头如下

那么既然使用UDP进行数据传输如此不可靠,为什么 HTTP/3 会使用UDP,事实上,这与整个互联网快速发展的大背景有关系,随着移动互联网快速发展以及物联网以及5G技术的逐步兴起,网络交互的场景越来越丰富,大量的音频、视频、直播等数据在网络传上输,用户对网络传输效率和 WEB 响应速度的要求也越来越高。HTTP/3 协议当然不能单独使用UDP协议,它必须在QUIC的配合下才可以使用UDP,其把数据的完整性校验这一环节放在了UDP协议之上,QUIC的特点:

  • 减少了 TCP 三次握手及 TLS 握手时间,使用TCP协议配合HTTPS,TLS 完全握手需要至少 2 个 RTT 才能建立,简化握手需要 1 个 RTT 的握手延迟,而QUIC在TLS握手时间上,由于建立在 UDP 的基础上,同时又实现了 0RTT 的安全握手,所以在大部分情况下,只需要 0 个 RTT 就能实现数据发送。

  • 改进的拥塞控制,QUIC 协议当前默认使用了 TCP 协议的 Cubic 拥塞控制算法,同时也支持 CubicBytes, Reno, RenoBytes, BBR, PCC 等拥塞控制算法。

  • 避免队头阻塞的多路复用,队头阻塞主要是 TCP 协议的可靠性机制引入的。上面说了TCP 为了实现可靠性,使用了很多机制来保障数据的传输,例如使用序列号来标识数据的顺序,数据必须按照顺序处理,如果前面的数据丢失,后面的数据就算到达了也不会通知应用层来处理 ,而QUIC使用UDP,没有三次握手和连接,只需要用户端和服务端的应用程序支持 QUIC 协议,完全避开了操作系统和中间设备的限制,同时相比此前的HTTP/2的多路复用,QUIC 一个连接上的多个流之间没有依赖。这样可以更快的并行处理任务。

  • 比TCP协议更安全,TCP 协议的头部没有加密和认证,在传输过程中很容易被篡改,注入和窃听。而 QUIC 除了个别报文比如 PUBLIC_RESET 和 CHLO,所有报文头部都是经过认证的,报文 Body 都是经过加密的。

  • 能够连接迁移,什么是连接迁移,比如使用手机从无线网切换到移动5G,这时客户端的 IP会改变,需要重新建立和服务端的 TCP 连接。而QUIC实现了任何一条 QUIC 连接不再以 IP 及端口进行标识,而是以一个 64 位的随机数作为 ID 来标识,这样当网络变化,IP和端口改变,只要 ID 不变,这条连接依然维持着,上层业务逻辑感知不到变化,不会中断,也就不需要重连。且由于这个ID 是随机的,产生冲突的概率非常小。

  • 更科学的流量控制器,TCP 为了保证可靠性,窗口左边沿向右滑动时的长度取决于已经确认的字节数。如果中间出现丢包,就算接收到了更大序号的 Segment,窗口也无法超过这个序列号。而QUIC 基于流和连接级别的流量控制,类似HTTP/2,通过window_update帧告诉对端自己可以接收的字节数,这样发送方就不会发送超过这个数量的数据。通过BlockFrame告诉对端由于流量控制被阻塞了,无法发送数据。就算此前有些数据包没有接收到,它的滑动只取决于接收到的最大偏移字节数。

如何使用 HTTP/3

好了,介绍了HTTP/3,那么我们怎么才能够使用HTTP/3呢?事实上,现在很多云厂商都实现了 HTTP/3,比如腾讯云的负载均衡器,阿里云的CDN服务、CloudFlare等等。那么如果你是使用类似Nginx 这样的web服务器如何使用HTTP/3呢?

以Nginx 为例,实际上,Nginx 在 2019 年 3 月 21 日公布了 1.17.x 版本的路线图,其中谈到了支持 QUIC 和 HTTP/3 的计划。但是至今,Nginx 已经发布了 1.17.8 的版本,在最新的1.17.8的 CHANGES 中也没有发现HTTP/3的身影。估计如果希望原生的 Nginx 支持 HTTP/3 还需要继续等待。

可喜的是边缘计算厂商 CloudFlare 开源了 QUIC 的实现 quiche,使得 Nginx 提前支持 HTTP/3 ,quiche 项目地址: https://github.com/cloudflare/quiche/tree/master/extras/nginx

CloudFlare 已经成功在其CDN业务中实现了QUIC的部署,基于HTTP3 Quic的实现靠的就是他们开发的Quiche实现。

根据 quiche 的文档,需要下载源码和 Nginx 一同编译,而因为需要 BoringSSL 以及 quiche 开发语言的不同,在编译 Nginx 所需要的环境基础上,还需要 cmake、rust、cargo、golang 等一系列工具的支持。

安装步骤

1.下载的nginx,并解压,注意,这个补丁只支持1.16.x

1
2
curl -O https://nginx.org/download/nginx-1.16.1.tar.gz
tar xzvf nginx-1.16.1.tar.gz

2.克隆quiche

1
git clone --recursive https://github.com/cloudflare/quiche
  1. 使用patch将补丁引入
1
2
cd nginx-1.16.1
patch -p01 < ../quiche/extras/nginx/nginx-1.16.patch

4.构建nginx, 使其支持 HTTP/3

1
2
3
4
5
6
7
8
./configure                                 \
--prefix=$PWD \
--build="quiche-$(git --git-dir=../quiche/.git rev-parse --short HEAD)" \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_v3_module \
--with-openssl=../quiche/deps/boringssl \
--with-quiche=../quiche

5.执行make

1
make

6.然后配置nginx的vhost,一个HTTP/3的配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
listen 80;
# Enable HTTP/2 (optional).
listen 443 ssl http2;
# Enable QUIC and HTTP/3.
listen 443 quic reuseport;

server_name www.awen.me;

root /vdata/www/default;
index index.html;

ssl_certificate /usr/local/nginx/ssl/awen.me.crt;
ssl_certificate_key /usr/local/nginx/ssl/awen.me.key;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ……

# Add Alt-Svc header to negotiate HTTP/3.
add_header alt-svc 'h3-23=":443"; ma=86400';
……
}

更具体的配置和说明,可以参考官方文档https://github.com/cloudflare/quiche/tree/master/extras/nginx

QUIC 的实现原理是首先客户端发起 tcp 连接判断服务端响应头是否有 alt-svc 头,如有则尝试使用 udp 443 去进行连接。因此,我们看到上面的配置中 add_header alt-svc 'h3-23=":443"; ma=86400';,客户端请求类似如下:

Alt-Svc 全称为“Alternative-Service”,直译为“备选服务”。该头部列举了当前站点备选的访问方式列表。一般用于在提供 “QUIC” 等新兴协议支持的同时,实现向下兼容。

h3-23=”:443” 这部分内容定义了替代服务使用的协议、主机名和端口,其中主机名和端口可选。多个替代服务之间用英文逗号分隔。

ma 是 max-age 的缩写,单位为秒。显然,它表示浏览器在指定时间内,可以直接使用替代服务地址。

客户端访问

1.浏览器配置

1.以chrome 为例进行配置,开启 quic 的支持,chrome://flags 安装如图所示 Experimental QUIC protocol 设置为 enable 重启浏览器

2.如何判断QUIC是否已经生效。

第一种方法是安装插件,在Chrome 商店安装 HTTP/2 and SPDY indicator,右上角有个闪电图标会显示 google 已经开启 quic/43版本的支持。如图所示:

img

打开 chrome://net-internals/#quic 也可以看到当前使用 quic 协议的站点连接情况。 第二种方式是通过wireshark抓包也可以看到QUIC的数据包,如图所示:

img