【问题标题】:How do I opt out of HTTP/2 server push when using fetch?使用 fetch 时如何选择退出 HTTP/2 服务器推送?
【发布时间】:2018-01-03 06:32:18
【问题描述】:

我正在用 Javascript 编写一个使用新的 fetch 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);
}

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

拒绝加载脚本“”,因为它违反了以下内容安全策略指令...

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

链接:;相对=预载; as=脚本

这是我的 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)的用例/图像/脚本。我(应用程序)以后不会使用任何相关资源;只有资源本身的内容。

在我的用例中,此推送 (1) 完全浪费资源,并且 (2) 现在导致控制台中偶尔出现一条非常烦人且会引起压力的消息。

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

我查看了https://w3c.github.io/preload/ 3.3 节,没有太大帮助。我看到我可以发送像Link: </dont/want/to/push/this>; rel=preload; as=script; nopush 这样的标题。问题是我还不知道响应中将包含哪些 Link 标头,并且我不确定 fetch 是否允许在初始请求中设置 Link 标头。我想知道我是否可以发送某种类型的请求,可以在响应中看到 Link 标头但避免它们,然后发送附加所有适当的 nopush 标头的后续请求?

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

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

补充说明:

  • 我不想使用代理服务器。一个明确的解释为什么这是我唯一的选择是一个可以接受的答案。
  • 我不知道在配置 CSP 时将获取的 url。
  • 参见https://www.rfc-editor.org/rfc/rfc7540#section-6.5.1,其中在相关部分声明“SETTINGS_ENABLE_PUSH (0x2):此设置可用于禁用服务器推送(第 8.2 节)。如果端点收到此参数设置为值,则不得发送 PUSH_PROMISE 帧的 0." 有没有办法从脚本或清单中指定此设置,或者将其烘焙到 Chrome 中?

【问题讨论】:

  • 为什么你不能只删除响应中的违规标头?
  • @dsign 根据developer.mozilla.org/en-US/docs/Web/API/Response/headers,Response.headers 是只读的。另外,我的理解是,这发生在收到响应时,我的脚本没有时间或地点跳到处理的中间并删除有问题的标头。浏览器处理所有这些。我只能在最后获得访问权限。
  • 抱歉不清楚。我的意思是,你为什么不能在服务器端删除有问题的标头?
  • 啊。好吧,我无法控制服务器或任何其他服务器。我正在用 javascript 编写一个应用程序,它将获取请求发送到各种其他服务器。甚至没有预先确定或预先编译的列表来说明与哪些其他服务器联系。
  • 好吧,我现在明白你的痛苦了。你确定你不会反对普通的CORS protections吗?

标签: javascript google-chrome-extension http2 fetch-api


【解决方案1】:

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

  1. 使用chrome.webRequest 拦截对扩展程序请求的响应。
  2. 使用onHeadersRecieved 的阻塞形式去除包含rel=preload 的标头
  3. 允许响应继续更新标头。

我不得不承认我花了很多时间试图弄清楚为什么这似乎有效,因为我不认为剥离链接标题应该在所有情况下都有效。我以为服务器推送会在请求发送后才开始推送文件。

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

此解决方案取决于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

  • 您可能还希望更具体地了解您要剥离的标头。我觉得剥离所有链接会产生一些额外的影响。

  • 这样可以避免代理,并且不需要在请求中设置链接头。

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

  • 我没有测试因阻止响应删除标头而导致的延迟。

【讨论】:

  • 感谢您的大力支持。我遇到了一些问题。 chrome.webRequest 需要权限。所以我尝试添加权限,现在 Chrome 拒绝加载扩展并说 webRequest 不能用于事件页面。我的背景页面是一个活动页面。嗯。
  • 我对事件页面的理解是它们是按需加载的,而 chrome.webRequest 有可能经常触发,以至于在延迟加载页面上没有意义。你能转换成背景页面吗?尽管首选事件页面,但背景页面仍然可以使用 "persistent": true 标志 more info
  • 谢谢,我正在调查这个。现在不能做太多,但如果它最终有效,将标记为已接受。
  • 您是否有机会看到如何轻松地将 url 限制为仅由扩展程序进行的特定提取,而不是所有请求?
  • 当然,这并不难,当您想从以下提取中清除链接标头时,您只需在foo() 中设置trackedUrl = url。对于您不想执行此操作的提取,请设置 trackedUrl = '&lt;&gt;' 或任何其他不会出现在 url 中的内容。结果是永远不会匹配带有无效字符的trackedUrl,因此不会清除链接头。如果您有更多明确的要求,或者如果您有多个同时请求,则有更好的方法来完成所有这些。这个微型 sn-p 的性能开销几乎为零。
猜你喜欢
  • 2020-02-23
  • 2017-09-06
  • 2015-12-08
  • 2018-07-26
  • 2020-03-24
  • 2017-04-06
  • 2019-05-24
  • 1970-01-01
  • 2017-03-04
相关资源
最近更新 更多