当 Transfer-Encoding: chunked 遇上 HTTP2

445 阅读6分钟

背景

某日,Leader 说:“给我们的 SSR 服务整一下流式渲染吧!”。听完我心想:这不是小弟已经研究过的玩意吗。所以果断回答 OK 没问题。

一天之后,本地修改完毕,部署到 Test 环境,开始验证!咦,返回的头部里面怎么没有 Transfer-Encoding: chunked 这个头部呀?鉴于我们的服务是部署在 Nginx 网关之后的,所以果断甩锅给运维,“怎么把我的头给弄丢了!”。

一番拉扯之后,运维甩过来一个链接:Transfer-Encoding。意思就是,这不是我们的锅呀,这个是 HTTP2 的规定。

所以现在的问题总结下来就是:

problem.png

问题原因知道了,那就面向 Google 开发呗,可是经过各种组合搜索后也没有找到相关的内容。没方法了,只能灰溜溜的跟 Leader 说做不了了。

但是这个问题一直萦绕在我脑海,害得我夜不能寐,饭不能食。毕竟,丢了的面子得找回来,而且不可能大家的服务都没有像这样部署过吧,肯定还是自己太菜了。

而且,根据上面这个链接所说:“HTTP2 提供了比 chunked 更为高效的数据流传输机制”,所以有可能 HTTP2 已经默默的帮我们做了些什么了?既然有此疑虑,直接验证一下呗。

验证

本文所需要的验证工具包括:

  • Nginx:需要开启 HTTP2,需要生成自签名证书,相关内容可自行搜索。这里仅给出配置文件:
events {https://juejin.cn/post/7133238781452222472
    worker_connections 2048;
}

http {
  server {
    ssl_certificate      mydomain.crt;
    ssl_certificate_key  private.key;

    listen 443 ssl http2;

    location / {
      proxy_http_version      1.1; # 注意这个参数不能省略,否则默认会用 1.0,1.0 不支持 chunked
      proxy_pass http://127.0.0.1:2048;
    }
  }
}
复制代码
  • 返回 chunked 内容的 server:本文使用 Node.js,代码如下:
const express = require('express')

const app = express()

app.get('/chunk_demo', (req, res, next) => {
  res.write(`<!DOCTYPE html>
      <html>
        <head>
          <title>Chunk Demo</title>
        </head>
        <body>
          <strong>你好`)

  setTimeout(() => {
    res.end(` World</strong>
    </body>
  </html>`)
  }, 4000)
})

const listener = app.listen(2048, () => {
  console.log('Your app is listening on port ' + listener.address().port)
})
复制代码
  • 支持 HTTP2 的客户端,这个一般的浏览器应该都支持。

然后,我们在浏览器中访问一下。从调试面板发现,协议确实是 HTTP2,且没有返回 Transfer-Encoding: chunked 头部,但是效果确实是我们想要的(不过这个浏览器貌似不太礼貌,不好好输出你好就算了,还骂我是“犲”。开个玩笑,这里显然是乱码了,文末有说明):

h2.png

header.png

chunked.gif

看来 HTTP2 确实自动做了些什么。

我们可以通过 wireshark 进一步的验证我们的想法,使用 wireshark 抓包需要配置其可以解密 HTTP2 流量,可参考使用 Wireshark 调试 HTTP/2 流量

如下所示,chunked 的两部分数据被 HTTP2 作为了两个 Data 帧进行传输,两个 Data 帧的时间相差 4 秒左右。

data1.png

data2.png

看来我们的 SSR 流式渲染可以顺利上线了,这个面子终于找回来了。

不过好不容易抓到了包,那就再顺手研究下 HTTP2 中 Header 和 Data 帧的数据格式吧。

补充内容

Header 和 Data 帧格式解析

首先,我们来看一下 GET /chunk_demo 这个 Header 帧的数据:

百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度