HTTP/2 服务器推送导致重复请求

Posted

技术标签:

【中文标题】HTTP/2 服务器推送导致重复请求【英文标题】:HTTP/2 Server Push results in duplicate requests 【发布时间】:2020-04-23 08:51:19 【问题描述】:

具有以下标题的文档的响应进入 nginx

link: </picture.jpg>; as=image; rel=preload
link: </_next/static/chunks/commons.4e96503c89eea0476d3e.module.js>; as=script; rel=preload
link: </_next/static/runtime/main-3c17cb16bbbc3efc4bb5.module.js>; as=script; rel=preload
link: </_next/static/runtime/webpack-0b10894b69bf5a718e01.module.js>; as=script; rel=preload
link: </_next/static/Q53NXtgLT1rgpqOOsVV6Q/pages/_app.module.js>; as=script; rel=preload
link: </_next/static/Q53NXtgLT1rgpqOOsVV6Q/pages/index.module.js>; as=script; rel=preload

在 HTTP/2 Server Push 的帮助下,请求被推送到客户端,但 6 个请求中有 5 个会下载两次(一次通过推送,一次由文档触发)。 Chrome 开发工具中的网络选项卡如下所示: 我已经测试了Type 是否设置正确并且看起来没问题。可能是什么问题?

连续请求(启用 chrome 缓存)也会产生类似的结果:

可能出了什么问题?我很确定请求不应该重复

@编辑 我尝试在没有 Nginx 的情况下进行服务器推送(直接与 Node.js 后端对话,而不是为 Nginx 附加链接头的后端)。它可以毫无问题地工作。当我使用 Nginx 时会出现问题。

顺便说一句。我知道不应该通过服务器推送推送所有内容,尤其是图片,但我这样做只是为了进行清晰的测试。如果仔细观察,似乎只有脚本被复制,图片只下载一次。

【问题讨论】:

您是否将这些作为匿名 XHR 请求加载? 是的,预加载链接和脚本标签都带有跨域匿名标签。我也尝试将这些更改为使用凭据,但问题仍然存在。 我很惊讶没有修复它。顺便说一句,您不应该需要预加载链接,因为您现在拥有执行相同操作的预加载标头。尝试完全删除它们。 是的,我也会尝试这样做它们是由我使用的库自动放置的 - Next.js 【参考方案1】:

您的 html 是在正常的“凭据”连接上请求的。然后它将 JPG 和 JS 推送到该连接上。

然后您的页面还会加载 via anonymous 跨域设置。所以它不能使用推送资源并再次请求它们。

更多详情请看这里:https://jakearchibald.com/2017/h2-push-tougher-than-i-thought/#requests-without-credentials-use-a-separate-connection

顺便说一句recommendations are only to push a small amount,而不是页面所需的所有资源。如果您甚至想使用 push,那是因为它很复杂,而且整体上并没有真正证明收益值得复杂性。

【讨论】:

我尝试在没有 Nginx 的情况下推送相同版本的应用程序(仅在我的后端处理 HTTP2 推送),并且以这种方式运行没有问题。请参阅图片的@edit。我会在nginx.conf 中遗漏一些东西吗?【参考方案2】:

问题的核心其实是 Chromium。据我所知,这件事只在 Chromium 中失败。

Nginx 的问题在于http2_push_preload 的实现。

Nginx 寻找的是带有Link: &lt;/resource&gt;; as=type; rel=preload 的标头。它读取它并通过推送提供文件,不幸的是,当浏览器(实际上我只测试了 Chrome)接收到带有 Link 标头的文档以及推送它冲突导致显着减速并下载看到的资源时而是解析文档。

# This results in HTTP/2 Server Push and the requests get duplicated due to the `Link` headers that were passed along
location / 
    proxy_pass http://localhost:3000;
    http2_push_preload on;


# This results in Resource Hints getting triggered in the browser.
location / 
    proxy_pass http://localhost:3000;


# This results in a regular HTTP/2 (no push)
location / 
    proxy_pass http://localhost:3000;
    http2_push_preload on;
    proxy_hide_header link;


# This result in a valid HTTP/2 Server Push (proper)
location / 
    proxy_pass http://localhost:3000;
    http2_push /commons.4e96503c89eea0476d3e.module.js;
    http2_push /index.module.js;
    http2_push /_app.module.js;
    http2_push /webpack-0b10894b69bf5a718e01.module.js;
    http2_push /main-3c17cb16bbbc3efc4bb5.module.js;

Nginx 似乎还不能很好地使用这个功能......

如果我可以删除 Link 标头并使用 http2_push_preload...

无论如何,我可以使用H2O H2O 确实让我在保留 HTTP/2 服务器推送的同时删除了标头

// h2o.conf
  [...]
  proxy.reverse.url: "http://host.docker.internal:3000/"
  header.unset: "Link"

与 H2O 一起工作正常: 我希望 Nginx 修复 http2_push_preload 的工作方式并允许更多控制。

另一方面,我认为 Chromium 无论如何都应该处理这个问题,而不是下载 2 倍的字节数。

【讨论】:

我确认http2_push_preload on 不能很好地与proxy_hide_header 配合使用。怎么可能还是这样?这是否意味着今天尝试在 nginx 上使用 http2_push_preload 的任何人都无法推送? 上次我检查它仍然坏了。我已经切换到 H2O 作为 HTTP/2 推送服务器。以这种方式使用 http2-casper 要好得多。 你后面还有 nginx,还是直接把 H2O 指向你的后端? 我直接喝了H2O

以上是关于HTTP/2 服务器推送导致重复请求的主要内容,如果未能解决你的问题,请参考以下文章

APNs Provider API HTTP/2 使用 php,curl 导致发送的多个推送通知出错

HTTP 2 将支持服务器推送,这是啥意思?

推送通知是不是会导致服务器上的连续轮询?

如何在 curl 中使用 HTTP/2 的推送功能?

HTTP 2.0 服务器推送技术的创新

Android推送SDK(9)-TCP网络问题