Как отключить HTTP/2-сервер при использовании выборки?
Я пишу базовое приложение в Javascript, которое использует новый API-интерфейс fetch. Вот базовый пример соответствующей части кода:
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);
}
При выполнении запроса на выборку я иногда вижу ошибки в консоли, которые выглядят следующим образом:
Отказано для загрузки script '<url> ' потому что он нарушает следующую директиву политики безопасности контента...
После некоторого чтения и изучения HTTP/2, похоже, что это сообщение появляется, потому что ответ отбрасывает предварительно загруженный script. Используя devtools, я могу увидеть следующий заголовок в ответе:
ссылка: < путь-to script > ; отно = предварительная нагрузка; а = script
Ниже приведена соответствующая часть моего файла расширения manifest.json.
{
"content_security_policy": "script-src 'self'; object-src 'self'"
}
Вот документация по формату Chrome manifest.json и как политика безопасности содержимого применяется к выборкам, сделанным расширением: https://developer.chrome.com/extensions/contentSecurityPolicy
Я провел некоторое тестирование и смог определить, что это сообщение об ошибке происходит во время выборки, а не позже при анализе текста ответа. Нет проблемы, когда элемент script загружается в живую DOM, все это происходит во время выборки.
То, что я не смог найти в своих исследованиях, заключалось в том, как избежать такого поведения. Похоже, что в спешке, чтобы поддержать эту замечательную новую функцию, люди, которые делали HTTP/2 и выборку, не рассматривали вариант использования, когда я не получаю удаленную страницу с целью ее отображения или любых связанных с ней ресурсов, таких как css/изображение/ script. Я (приложение) никогда не будет использовать какой-либо связанный ресурс; только содержимое самого ресурса.
В моем случае использования этот push (1) представляет собой полную трату ресурсов и (2) теперь вызывает случайное появление раздражающего и вызывающего стресс сообщения в консоли.
С учетом сказанного, вот вопрос, который мне бы очень понравился: есть ли способ сообщить браузеру, используя манифест или script, что я не заинтересован в HTTP/2 push? Есть ли заголовок, который я могу задать для запроса на выборку, который сообщает веб-серверу, что он не отвечает нажатием? Есть ли параметр CSP, который я могу использовать в своем манифесте приложения, который каким-то образом вызывает ответ do-not-push-me?
Я просмотрел https://w3c.github.io/preload/ раздел 3.3, это не помогло. Я вижу, что я могу отправлять заголовки типа Link: </dont/want/to/push/this>; rel=preload; as=script; nopush
. Проблема в том, что я еще не знаю, какие заголовки ссылок будут в ответе, и я не уверен, что выборка даже разрешает установку заголовков Link в первоначальном запросе. Интересно, могу ли я отправить какой-то запрос, который может видеть заголовки Link в ответе, но избегает их, а затем отправить запрос на последующий запрос, который добавляет все соответствующие заголовки nopush?
Вот простой тестовый пример, чтобы воспроизвести проблему:
- Получить версию разработчика последней или самой последней версии chrome
- Создать папку расширения
- Создать манифест с аналогичным CSP
- Расширение загрузки как распакованное в хром
- Откройте базовую страницу для расширения в devtools
- В консоли типа fetch ('https://www.yahoo.com').
- Изучите полученное сообщение об ошибке, появившееся в консоли: Отказано для загрузки script 'https://www.yahoo.com/sy/rq/darla/2-9-20/js/g-r-min.js', поскольку оно нарушает следующие Политика политики безопасности содержимого: "script -src" self ".
Дополнительные примечания:
- Я не хочу использовать прокси-сервер. Ясное объяснение того, почему это будет моим единственным вариантом, будет приемлемым ответом.
- Я не знаю URL-адреса, которые будут выбраны во время настройки CSP.
- См. https://tools.ietf.org/html/rfc7540#section-6.5.1, в котором говорится, что" SETTINGS_ENABLE_PUSH (0x2): этот параметр можно использовать для отключения нажатия на сервер (раздел 8.2). Конечная точка НЕ ДОЛЖНА отправлять кадр PUSH_PROMISE, если он принимает этот параметр, чтобы установить значение
0. "Есть ли способ указать этот параметр из script или манифест или он испечен в Chrome?
Ответы
Ответ 1
После того, как вы последовали вместе со своим тестовым примером, я смог решить эту проблему (пример) следующим образом, хотя я не знаю, что это применимо ко всем более общим случаям:
- Используйте
chrome.webRequest
для перехвата ответов на запросы расширения.
- Используйте блокирующую форму
onHeadersRecieved
для выделения заголовков, содержащих rel=preload
- Разрешить ответу с обновленными заголовками.
Я должен признать, что я потратил много времени, пытаясь понять, почему это, похоже, работает, поскольку я не думаю, что зачистка заголовков Link должна работать во всех случаях. Я думал, что Server Push просто начнет толкать файлы после отправки запроса.
Как вы упомянули в своей дополнительной заметке о SETTINGS_ENABLE_PUSH
, большая часть этого на самом деле испечена на хроме и скрыта от нашего взгляда. Если вы хотите копать глубже, я нашел подробности в chrome://net-internals/#http2
. Возможно, Chrome убивает файлы, отправленные Server Push, которые не имеют соответствующего заголовка Link в исходном ответе.
Это решение зависит от chrome.webRequest
Docs
Фон расширения script:
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
, вы можете проверить здесь
-
Вы, вероятно, также захотите быть более конкретными в отношении того, какие заголовки вы снимаете. Я чувствую, что зачистка всех ссылок будет иметь некоторые дополнительные эффекты.
-
Это позволяет избежать прокси и не требует установки заголовка Link в запросе.
-
Это делает довольно мощное расширение, лично я избегаю расширений с разрешениями, например <all_urls>
, надеюсь, вы можете сузить область действия.
-
Я не тестировал задержки, вызванные блокировкой ответов для удаления заголовков.