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

4
以下是关于具有以下标头的文档的响应进入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服务器推送的帮助,请求被推送到客户端,但其中5个请求下载了两次(一次是推送,一次是由文档触发)。 在Chrome Dev工具中的网络选项卡如图所示: network graph 我已经测试了Type是否设置正确,看起来没问题。可能出了什么问题?
连续的请求(启用Chrome缓存)也会以类似的方式产生结果: network graph with cache enabled 可能出了什么问题?我非常确定请求不应该重复。
@edit 我尝试进行无Nginx的服务器推送(直接与Node.js后端通信,而不是后端附加链接头文件给Nginx)。这没有问题。当我使用Nginx时,问题就出现了。 push via nodejs 顺便说一句,我知道不应该通过服务器推送所有内容,特别是图片,但我只是为了进行清晰的测试而这样做。如果你仔细看,似乎只有脚本被复制了,图片只下载了一次。

你是将它们作为匿名的 XHR 请求加载的吗? - Barry Pollard
是的,预加载链接和脚本标签都带有跨域匿名标记。我也尝试将它们更改为使用凭据,但问题仍然存在。 - Dawid
我很惊讶那没有解决问题。你不需要预加载链接,因为你现在有预加载头,它们可以完成同样的工作。尝试完全删除它们。 - Barry Pollard
是的,我也会尝试做到这一点。 它们是由我使用的库 - Next.js自动放置的。 - Dawid
2个回答

3
问题的核心实际上是Chromium。据我所见,只有在Chromium中出现了这个问题。
Nginx的问题在于http2_push_preload的实现。
Nginx需要一个带有Link: </resource>; as=type; rel=preload头的请求。它会读取并通过push服务提供文件。不幸的是,当浏览器(我只测试了Chrome)接收到包含Link头以及Push的文档时,会发生冲突,导致显著减速,并下载解析文档时看到的资源。
# 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 中表现良好: H2O success 我希望 Nginx 修复 http2_push_preload 的工作方式并允许更多的控制。

此外,我认为Chromium 应该解决这个问题,而不是下载两倍于原本的数据。


我确认 http2_push_preload onproxy_hide_header 不兼容。为什么会这样?这是否意味着今天任何试图在 nginx 上使用 http2_push_preload 的人都会遇到推送失败的问题? - cherouvim
1
上次我检查时它仍然无法正常工作。我已经切换到H2O作为HTTP/2推送服务器。在这方面,它比http2-casper要优秀得多。 - Dawid
你是否仍然在使用nginx,还是直接将H2O指向了你的后端? - cherouvim
我直接使用了H2O。 - Dawid

1

您的HTML被请求在普通的“凭证”连接上,然后将JPG和JS推送到该连接上。

您的页面还通过匿名跨源设置加载。因此,它无法使用推送的资源并再次请求它们。

更多详细信息请参见:https://jakearchibald.com/2017/h2-push-tougher-than-i-thought/#requests-without-credentials-use-a-separate-connection

顺便提一下,推荐只推送少量资源,而不是页面所需的每个资源。如果您确实想要使用push,则需要考虑其复杂性以及整体上未被证明的收益是否值得。


1
我尝试推送没有Nginx的同一版本应用程序(仅在我的后端处理HTTP2 Push),这样可以正常工作。请参见图片的@edit。我在nginx.conf中漏掉了什么吗? - Dawid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接