使用 fetch 时如何选择退出 HTTP/2 服务器推送?
Posted
技术标签:
【中文标题】使用 fetch 时如何选择退出 HTTP/2 服务器推送?【英文标题】:How do I opt out of HTTP/2 server push when using fetch? 【发布时间】: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 标头的后续请求?
这是一个重现问题的简单测试用例:
-
获取最新或接近最新的 chrome 开发版
创建扩展文件夹
创建具有相似 CSP 的清单
将扩展加载为解压到 chrome 中
在 devtools 中打开扩展的后台页面
在控制台中输入 fetch('https://www.yahoo.com')。
检查控制台中出现的错误消息:拒绝加载脚本'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吗? 【参考方案1】:遵循您的测试用例后,我能够通过以下方式解决此(示例)问题,但我不知道它是否适用于所有更一般的情况:
-
使用
chrome.webRequest
拦截对扩展程序请求的响应。
使用onHeadersRecieved
的阻塞形式去除包含rel=preload
的标头
允许响应继续更新标头。
我不得不承认我花了很多时间试图弄清楚为什么这似乎有效,因为我不认为剥离链接标题应该在所有情况下都有效。我以为服务器推送会在请求发送后才开始推送文件。
正如您在关于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
您可能还希望更具体地了解您要剥离的标头。我觉得剥离所有链接会产生一些额外的影响。
这样可以避免代理,并且不需要在请求中设置链接头。
这是一个非常强大的扩展,我个人避免使用像<all_urls>
这样的权限的扩展,希望你能缩小范围。
我没有测试因阻止响应删除标头而导致的延迟。
【讨论】:
感谢您的大力支持。我遇到了一些问题。 chrome.webRequest 需要权限。所以我尝试添加权限,现在 Chrome 拒绝加载扩展并说 webRequest 不能用于事件页面。我的背景页面是一个活动页面。嗯。 我对事件页面的理解是它们是按需加载的,而 chrome.webRequest 有可能经常触发,以至于在延迟加载页面上没有意义。你能转换成背景页面吗?尽管首选事件页面,但背景页面仍然可以使用"persistent": true
标志 more info
谢谢,我正在调查这个。现在不能做太多,但如果它最终有效,将标记为已接受。
您是否有机会看到如何轻松地将 url 限制为仅由扩展程序进行的特定提取,而不是所有请求?
当然,这并不难,当您想从以下提取中清除链接标头时,您只需在foo()
中设置trackedUrl = url
。对于您不想执行此操作的提取,请设置 trackedUrl = '<>'
或任何其他不会出现在 url 中的内容。结果是永远不会匹配带有无效字符的trackedUrl,因此不会清除链接头。如果您有更多明确的要求,或者如果您有多个同时请求,则有更好的方法来完成所有这些。这个微型 sn-p 的性能开销几乎为零。以上是关于使用 fetch 时如何选择退出 HTTP/2 服务器推送?的主要内容,如果未能解决你的问题,请参考以下文章
PHP - 选择然后插入时是不是需要 fetch_array
选择另一个 UITextView 时,如何阻止 UITextView 退出第一响应者/关闭键盘?