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

HTTP/2 Server Push results in duplicate requests

A response for a document with the following headers enter 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 服务器推送的帮助下,请求被推送到客户端,但是 6 个请求中有 5 个下载了两次(一次是推送,一次是文档触发)。 Chrome Dev Tools 中的网络选项卡如下所示: 我已经测试了 Type 是否设置正确并且看起来没问题。可能是什么问题?

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

有什么问题吗?我很确定请求不应该重复

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

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

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

然后您的页面还会通过 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 而不是页面需要的所有资源。如果你甚至想使用推送,那是因为它很复杂,而且总体上还没有真正证明收益值得复杂性。

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

Nginx 的问题在于 http2_push_preload 的实现。

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

# 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 header 并使用 http2_push_preload...

就好了

无论如何我都可以使用 H2O H2O 确实让我在保留 HTTP/2 服务器推送

的同时删除了 headers
// h2o.conf
  [...]
  proxy.reverse.url: "http://host.docker.internal:3000/"
  header.unset: "Link"

适用于 H2O: 我希望 Nginx 修复 http2_push_preload 的工作方式并允许更多控制。

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