使用 fetch 时如何选择退出 HTTP/2 服务器推送?

How do I opt out of HTTP/2 server push when using fetch?

我正在 Javascript 中编写一个使用新提取 API 的基本应用程序。这是代码相关部分的基本示例:

function foo(url) {
  const options = {};
  options.credentials = 'omit';
  options.method = 'get';
  options.headers = {'Accept': 'text/html'};
  options.mode = 'cors';
  options.cache = 'default';
  options.redirect = 'follow';
  options.referrer = 'no-referrer';
  options.referrerPolicy = 'no-referrer';
  return fetch(url, options);
}

发出提取请求时,我偶尔会在控制台中看到如下所示的错误:

Refused to load the script '<url>' because it violates the following Content Security Policy directive ...

阅读和了解 HTTP/2 后,似乎出现此消息是因为响应正在推回预加载的脚本。使用 devtools,我可以在响应中看到以下 header:

link:<path-to-script>; rel=preload; as=script

这是我的 Chrome 扩展的 manifest.json 文件的相关部分:

{
  "content_security_policy": "script-src 'self'; object-src 'self'"
}

这里是关于 Chrome 的 manifest.json 格式的文档,以及如何将内容安全策略应用于扩展所做的提取:https://developer.chrome.com/extensions/contentSecurityPolicy

我做了一些测试,能够确定此错误消息是在获取期间发生的,而不是稍后在解析响应文本时发生的。将脚本元素加载到实时 DOM 没有问题,这一切都发生在获取时。

我在研究中未能找到的是如何避免这种行为。看起来急于支持这个伟大的新功能,制作 HTTP/2 和 fetch 的人没有考虑我不为了显示它或其任何相关资源而获取远程页面的用例像 css/image/script。我(应用程序)以后不会再使用任何相关资源;只有资源本身的内容。

在我的用例中,此推送 (1) 完全是资源浪费,并且 (2) 现在导致非常烦人且 stress-inducing 消息偶尔出现在控制台中。

话虽如此,我希望得到一些帮助的问题是:有没有一种方法可以使用清单或脚本向浏览器发出信号,表明我对 HTTP/2 推送不感兴趣?是否有 header 可以设置为告诉 Web 服务器不响应推送的获取请求?我可以在我的应用程序清单中使用以某种方式触发 do-not-push-me 响应的 CSP 设置吗?

我看过 https://w3c.github.io/preload/ 第 3.3 节,没有多大帮助。我看到我可以像 Link: </dont/want/to/push/this>; rel=preload; as=script; nopush 一样发送 header。问题是我不知道哪个 Link headers 会出现在响应中,而且我不确定 fetch 是否允许设置 Link headers 在初始请求。我想知道我是否可以发送某种类型的请求,可以在响应中看到 Link headers 但避免它们,然后发送一个附加所有适当的 nopush headers 的后续请求?

这是一个重现问题的简单测试用例:

  1. 获取最新或接近最新的开发版本chrome
  2. 创建扩展文件夹
  3. 使用类似的 CSP 创建清单
  4. 将解压后的扩展加载到 chrome
  5. 在开发者工具中打开扩展的后台页面
  6. 在控制台中输入 fetch('https://www.yahoo.com').
  7. 检查控制台中出现的错误消息:拒绝加载脚本 'https://www.yahoo.com/sy/rq/darla/2-9-20/js/g-r-min.js' 因为它违反了以下内容安全策略指令:“script-src 'self'".

补充说明:

按照您的测试用例进行操作后,我能够通过以下方式解决此(示例)问题,但我不知道它是否适用于所有更一般的情况:

  1. 使用 chrome.webRequest 拦截对扩展请求的响应。
  2. 使用 onHeadersRecieved 的分块形式去除包含 rel=preload
  3. 的 header
  4. 允许响应继续更新的 headers。

我不得不承认我花了很多时间试图弄清楚为什么这似乎有效,因为我不认为剥离 Link headers 在所有情况下都有效。我还以为Server Push会在请求发出后才开始推送文件呢

正如您在关于 SETTINGS_ENABLE_PUSH 的附加说明中提到的那样,其中大部分内容实际上已融入 chrome 并且我们无法看到。如果您想深入挖掘,我在 chrome://net-internals/#http2 找到了详细信息。也许 Chrome 正在杀死服务器推送发送的文件,这些文件在初始响应中没有相应的 Link header。

此解决方案取决于 chrome.webRequest Docs


扩展的后台脚本:

let trackedUrl;

function foo(url) {
  trackedUrl = url;
  const options = {};
  options.credentials = 'omit';
  options.method = 'get';
  options.headers = { 'Accept': 'text/html' };
  options.mode = 'cors';
  options.cache = 'default';
  options.redirect = 'follow';
  options.referrer = 'no-referrer';
  options.referrerPolicy = 'no-referrer';
  return fetch(url, options)
}

chrome.webRequest.onHeadersReceived.addListener(function (details) {
  let newHeaders;
  if (details.url.indexOf(trackedUrl) > -1) {
    newHeaders = details.responseHeaders.filter(header => {
      return header.value.indexOf('rel=preload') < 0;
    })
  }

  return { responseHeaders: newHeaders };
}, { urls: ['<all_urls>'] }, ['responseHeaders', 'blocking']);

扩展的清单:

{
  "manifest_version": 2,
  "name": "Example",
  "description": "WebRequest Blocking",
  "version": "1.0",
  "browser_action": {
    "default_icon": "icon.png"
  },
  "background": {
    "scripts": [
      "back.js"
    ]
  },
  "content_security_policy": "script-src 'self'; object-src 'self'",
  "permissions": [
    "<all_urls>",
    "background",
    "webRequest",
    "webRequestBlocking"
  ]
}

补充说明:

  • 我只是天真地将其限制为来自扩展的最新请求 url,webRequest.requestFilters 被烘焙到 chrome.webRequest 你可以查看 here

  • 您可能还想更具体地说明要剥离哪些 header。我觉得剥离所有 Links 会有一些额外的效果。

  • 这避免了代理并且不需要在请求中设置 Link header。

  • 这是一个非常强大的扩展,我个人避免使用像<all_urls>这样的权限扩展,希望你能缩小范围。

  • 我没有测试阻止响应删除 headers 造成的延迟。