HTTP/3 用起来

5,628 阅读7分钟

地址: www.sunbohao.com/font

测试方法可以 http3check.net/ 检测网站是否支持 HTTP/3

或者:打开 chrome://flags/ 找到 Experimental QUIC protocol 选择 enabled 重启浏览器, Firefox / Safari 可以参考 这篇文章

HTTP/3介绍

HTTP/3 是即将到来的(目前草案阶段) 第三个 HTTP 协议主要版本,与其前任 HTTP/1.1 HTTP/2 不同,在 HTTP/3 中,将弃用 TCP 协议,改为使用基于 UDP 协议的 QUIC 协议实现

为了解释为什么 HTTP/3 改用 QUIC 实现,我们需要简单回顾一下 HTTP 的历史

HTTP/0.9

1991年 Tim Berners-Lee 设计了一个 简单的超文本传输协议原型,后来被称为 HTTP/0.9

此时 tcp 已经是一个可靠、成熟的协议,文档中特别提到:"HTTP 目前运行在 TCP 上,但可以运行在任何面向连接的服务上"

当然此时的 HTTP 非常简单,没有 header,没有状态码,只有一个简单的:

   GET /index.html

响应只包含 html,并以关闭 TCP 结束

此时的单个html,提供了一个完整的、自给自足的页面

HTTP/1.0

在随后的几年里,互联网呈现爆炸式的发展,HTTP发展成为了一种可扩展、灵活的通用协议,尽管传输 html 仍是它的专长

HTTP有三个关键的实现,完成了这一演变:

1. 引入了 POST 和 HEAD 命令, 丰富了浏览器与服务器交互的手段。

2. 状态码的引入,为客户端提供了一种识别服务器是否成功处理请求,以及发生了哪些错误的方法,

3. 请求和回应格式的改变,每次通信除了数据部分,还必须包含头部信息(HTTP header),用来描述元数据。例如:Content-Type,允许HTTP不仅可以传输HTML,还可以传输任何类型的有效负载,Content-Encoding,说明了数据的压缩算法,允许客户端和服务器协商压缩算法,从而减少了传输数据量

同时,HTML支持了 图像、样式和其他链接资源,浏览器被迫执行多个请求来显示单个页面,而原来的每次请求都建立 TCP 链接,随着页面的请求数量增加,延迟也随之增大,下图是为每个请求都建立一个新的 TCP 连接所涉及的开销

为了解决这个问题,有些浏览器自行实现了一个非标准的 Connection 字段

  Connection: keep-alive

这个字段要求服务器不要关闭 TCP 连接,以便后续请求复用,服务端也回应这个字段,一个可复用的TCP连接就建立了,直到客户端或者服务器主动关闭连接,但是这不是标准的字段,不同的实现的行为可能不一致

HTTP/1.1

HTTP/1.1 修复了 HTTP/1.0 的不一致性,并对协议进行了调整,使其在新的web生态系统中具有更好的性能。引入的两个最关键的变化是默认使用持久TCP连接和HTTP管道。

HTTP管道仅仅意味着,客户端在发送后续请求时,不需要等待服务器对之前请求的响应,这个特性有效的利用了带宽并减少了延迟,但是HTTP管道仍然要求服务器按照接收请求的顺序响应,所以如果在管道中存在某个响应执行缓慢,那么后续的所有对客户端的响应都将延迟,这个问题被称为队头阻塞

此时,Web正在获得越来越多的交互式功能,一些网页包含数百个外部资源,为了解决队头阻塞的问题,降低页面延迟,客户端在每个主机上建立多个 TCP 连接,当然新建连接的开销依然存在,特别是 SSL/TSL 普及的情况下,所以实际情况下新建连接情况可能更加糟糕,因此大多数浏览器设置了最大连接限制,以平衡这一点(那些年克服延迟之道)

SPDY and HTTP/2

2009年, 谷歌公开了自行研发的SPDY协议,主要解决HTTP/1.1效率不高的问题,这个协议在Chrome浏览器上证明可行之后, 就被当做HTTP/2的基础

HTTP/2标准在SPDY的基础上进行了一些改进,HTTP/2通过在一个 TCP 连接上多路复用 HTTP请求,解决了队头阻塞的问题,这允许服务器可以以任何顺序应答,然后客户端重新组装响应

此外 HTTP/2 允许服务器主动推送数据;除了压缩数据体之外,还允许压缩请求头,这进一步减少了传输量

HTTP/2 解决了大部分问题,但并不是所有问题,在 TCP 协议级别仍然存在着线路问题:

采用HTTP/2时,浏览器一般会在单个TCP连接中创建并行的几十个乃至上百个传输。如果HTTP/2连接双方的网络中有一个数据包丢失,或者任何一方的网络出现中断,整个TCP连接就会暂停,丢失的数据包需要被重新传输。因为TCP是一个按序传输的链条,因此如果其中一个点丢失了,链路上之后的内容就都需要等待。这种单个数据包造成的阻塞,就是TCP上的队头阻塞(head of line blocking)。

随着丢包率的增加,HTTP/2的表现越来越差。在2%的丢包率(一个很差的网络质量)中,测试结果表明HTTP/1.1用户的性能更好,因为HTTP/1.1一般有六个TCP连接,哪怕其中一个连接阻塞了,其他没有丢包的连接仍然可以继续传输。

HTTP/3

由于 HTTP/2 的问题仅仅在应用层是无法解决的,因此新的协议迭代必须更换传输层,UDP协议和TCP一样受到广泛支持,UDP数据包是即发即忘的,没有握手、持久连接和错误重试,HTTP/3 主要思想是放弃 TCP 而支持基于 UDP 的 QUIC 协议

与 HTTP/2 允许未加密不同,QUIC 严格要求加密建立连接,此外加密应用于所有流过连接的数据,而不仅仅是 http 的有效负载

为了解决传输层队头阻塞问题,HTTP/3通过 QUIC 在每个连接中把数据分为单独的数据流,数据流是短暂的"子链接",每个流处理自己的错误重试,每个http请求运行在单独的流上,因此数据包的丢失不会影响其它请求的传输

UDP是一种无状态协议(持久连接只是一层抽象),使得 QUIC 在很大程度上能够忽略数据包的复杂性,例如,客户端在连接过程中更改 ip 地址(比如智能手机从移动网络跳转到家庭wifi),理论上不应该中断连接,因为协议允许在不同IP地址之间迁移而无需重新连接。

HTTP/3实践

nginx官方也支持了 HTTP/3 的预览版

不过在这里我直接使用现成的 nginx-http3 docker镜像,

添加nginx配置:

    # Enable QUIC and HTTP/3.
    listen 443 quic reuseport;


    # Enable HTTP/2 (optional).
    listen 443 ssl http2;


    ssl_certificate      xxx.com.pem;
    ssl_certificate_key  xxx.com.key;


    # 需要配置 TLS1.3, ssl 相关配置 我都放在了 inc/ssl.conf 中,这里为了单独展示提出来了
    ssl_protocols    TLSv1.2 TLSv1.3; # QUIC requires TLS 1.3
    # 添加 alt-svc 返回头, 这里使用 google 默认的返回头信息
    add_header alt-svc 'h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"';

此外开放云服务的 443/udp 端口

此外,这次优化算是继上次优化之后的又一进步:

打工人,加油~~~~~~

参考资源:

www.sunbohao.com/font/conten…

scorpil.com/post/the-lo…

www.w3.org/Protocols/H…

github.com/cloudflare/…

http3-explained.haxx.se/zh

http2-explained.haxx.se/zh

zh.wikipedia.org/wiki/HTTP/3

blog.cjw.design/blog/devopt…

www.bram.us/2020/04/08/…

追加更新:

HTTP/3  2022年6月 已标准化为RFC 9114

需要改一下返回头

add_header alt-svc 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"';

正常打开 chrome