HTTP/2(超文本传输协议第 2 版,最初命名为 HTTP 2.0),简称为 h2(基于 TLS/1.2 或以上版本的加密连接)或 h2c(非加密连接),是 HTTP 协议的的第二个主要版本,使用于万维网。
在了解 HTTP/2 新特性前,可以通过 DEMO 直观感受 HTTP/1.1 与 HTTP/2 的差距:
HTTP/2: the Future of the Internet | Akamai
主要新特性:
相比于 HTTP/1.x 基于以换行符作为纯文本的分隔符的解析,HTTP/2 将所有的传输信息分割为更小的消息和帧,并对它们采用 二进制格式编码。基于二进制可以使协议有更多的扩展性,例如,引入帧来传输数据和指令。
HTTP/2 所有性能增强的核心在于新的 二进制分帧层,它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。
这里所谓的 层,指的是位于套接字接口与应用可见的高级 HTTP API 之间一个经过优化的新编码机制:HTTP 的语义(包括各种动词、方法、标头)都不受影响,不同的是传输期间对它们的编码方式变了。
新的二进制分帧机制改变了客户端与服务器之间交换数据的方式。
为了说明这个过程,我们需要了解 HTTP/2 的三个概念:
这些概念的关系总结如下:
HTTP/2 将 HTTP 协议通信分解为二进制编码帧的交换,这些帧对应着特定数据流中的消息。所有这些都在一个 TCP 连接内复用,这是 HTTP/2 协议所有其他功能和性能优化的基础。
消息的组成:HEADERS 帧与 DATA 帧
更多关于帧定义详解,请查阅 HTTP/2 中的帧定义
标头压缩(Header compression):每个 HTTP 传输都承载一组标头,这些标头说明了传输的资源及其属性。 在 HTTP/1.x 中,此元数据始终以纯文本形式,通常会给每个传输增加 500–800 字节的开销。如果使用 HTTP Cookie,增加的开销有时会达到上千字节。
为了减少此开销和提升性能,HTTP/2 使用 HPACK 压缩格式压缩请求和响应标头元数据,这种格式采用两种简单但是强大的技术:
利用霍夫曼编码(HPACK),可以在传输时对各个值进行压缩,而利用之前传输值的索引列表,我们可以通过传输索引值的方式对重复值进行编码,索引值可用于有效查询和重构完整的标头键值对。
索引表用法示意
客户端发了两次请求,第一次请求有完整的 HTTP 报文头部,第二次请求的时候只有一个 path
的字段不一样,但是这次报文头它只需要发送一个 path
的字段就好了,这样就大大减少了发送的量。这个的实现要求客户端和服务同时维护一个报文头表。
HPACK 压缩示意:
作为一种进一步优化方式,HPACK 压缩上下文包含一个静态字典和一个动态字典:
因此,为之前未见过的值采用静态 Huffman 编码,并替换每一侧静态表或动态表中已存在值的索引,可以减小每个请求的大小。
⚠️ 注意:在 HTTP/2 中,请求和响应标头字段的定义保持不变,仅有一些微小的差异:所有标头字段名称均为小写,请求行现在拆分成各个
:method
、:scheme
、:authority
和:path
伪标头字段。
多路复用(MultiPlexing):通过该功能,在一条连接上,您的浏览器可以同时发起无数个请求,并且响应可以同时响应。另外,多路复用中支持了流的优先级(Stream dependencies)设置,允许客户端告知服务器最优资源,可以优先传输。
当我们打开网站时,浏览器会对每个网页并发的连接进行限制,一般浏览器的 HTTP 请求并发数限制在 6-8 个。但实际上,绝大部分网站首页所需要的资源个数远大于这个限制。所以为了不让资源在下载阶段就被阻塞住,我们往往会把一些静态资源分散到 CDN 或其他服务器上,从而通过多域名的方式突破浏览器对并发连接数的限制,从而使得网站能同时下载尽可能多的资源。
但建立更多的连接也意味更多的开销。每个 HTTP 请求都对应建立 TCP 连接,也许有些资源体积只有几 kb,这些情况下建立连接本身的开销就变得更客观,很可能三次握手的实践比传输时间还长
在 HTTP/2 中,有了二进制分帧之后,HTTP/2 不再依赖 TCP 链接去实现多流并行,而是通过 流 支持多路复用。
将 HTTP 消息分解为独立的帧,交错发送,然后在另一端重新组装是 HTTP/2 最重要的一项增强。事实上,这个机制会在整个网络技术栈中引发一系列连锁反应,从而带来巨大的性能提升,让我们可以:
HTTP/2 中的新二进制分帧层解决了 HTTP/1.x 中存在的 队头阻塞 问题,也消除了并行处理和发送请求及响应时对多个连接的依赖。
服务端推送(Server push):同 SPDY 一样,HTTP/2 也具有客户端推送功能。目前,大多数网站已经启用 HTTP/2,如淘宝。使用服务器推动,可以提前将资源推送至浏览器缓存。
HTTP/2 中的 Server Push 并不是指类似于现在的 Server Sent Event(SSE) 或者 WebSocket 的推送技术。它是一种服务器根据客户端以前发送的请求来猜测未来的请求,并提前将未来请求的结果推送给客户端的技术。
为什么在浏览器中需要一种此类机制呢?
一个典型的网络应用包含多种资源,客户端需要检查服务器提供的文档才能逐个找到它们。 那为什么不让服务器提前推送这些资源,从而减少额外的延迟时间呢? 服务器已经知道客户端下一步要请求什么资源,这时候服务器推送即可派上用场。
事实上,如果您在网页中内联过 CSS、JavaScript,或者通过数据 URI 内联过其他资产(请参阅 资源内联),那么您就已经亲身体验过服务器推送了。 对于将资源手动内联到文档中的过程,我们实际上是在将资源推送给客户端,而不是等待客户端请求。 使用 HTTP/2,我们不仅可以实现相同结果,还会获得其他性能优势。
推送资源可以进行以下处理:
通过 Nginx 配置能主动推送两个 HTML 文档中需要请求的文件:
server {# 设定开启使用 HTTP2listen 443 ssl http2;server_name localhost;ssl on;ssl_certificate /etc/nginx/certs/example.crt;ssl_certificate_key /etc/nginx/certs/example.key;ssl_session_timeout 5m;ssl_ciphers HIGH:!aNULL:!MD5;ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;ssl_prefer_server_ciphers on;location / {root /usr/share/nginx/html;index index.html index.htm;# 服务器主动推送这两个文件http2_push /style.css;http2_push /example.png;}
这种方式需要写在服务器配置中,每次修改都要重启服务,而且应用与服务器的配置不应该混在一起。
服务器推送还有另一个实现方法,就是后端应用产生 HTTP 响应头信息 Link
命令。服务器发现有这个头信息,就会进行服务器推送。
Link: </styles.css>; rel=preload; as=style
如果要推送多个资源:
Link: </styles.css>; rel=preload; as=style, </example.png>; rel=preload; as=image
这时,Nginx 的配置改成下面这样。
server {listen 443 ssl http2;# ...root /var/www/html;location = / {proxy_pass http://upstream;http2_push_preload on;}}
如果服务器或者浏览器不支持 HTTP2,那么浏览器就会按照 preload
来处理这个头信息,预加载指定的资源文件。
事实上,这个头信息就是 preload
标准提出的,它的语法和 as
属性的值都写在了 标准 里面。
参考 HTTP/2 Server Push with Node.js 实现。
服务器推送有一个很麻烦的问题。所要推送的资源文件,如果浏览器已经有缓存,推送就是浪费带宽。即使推送的文件版本更新,浏览器也会优先使用本地缓存。
一种解决办法是,只对第一次访问的用户开启服务器推送。下面是 Nginx 官方给出的示例,根据 Cookie 判断是否为第一次访问。
server {listen 443 ssl http2 default_server;ssl_certificate ssl/certificate.pem;ssl_certificate_key ssl/key.pem;root /var/www/html;http2_push_preload on;location = /demo.html {add_header Set-Cookie "session=1";add_header Link $resources;}}map $http_cookie $resources {"~*session=1" "";default "</style.css>; as=style; rel=preload";}
HTTP/2 是不是必须基于 TLS/SSL 协议?
http://
和 https://
默认基于 80 和 443 端口h2
h2c