Некоторое время назад я видел статью в СМИ«Node.js может использовать HTTP/2!». Когда я вижу слово push, я думаю о push-сообщении WebSocket. Возможно ли, что HTTP/2 может активно отправлять сообщения с сервера, такого как WebSocket? Это так круто, что мне сразу стало интересно.
Однако, прочитав статью, я обнаружил, что между идеалом и реальностью есть небольшой разрыв. Проще говоря, так называемый серверный толчок HTTP/2 на самом деле заключается в том, что когда сервер получает запрос, он может ответить нескольким ресурсам. Например, браузер запрашивает index.html с сервера, а сервер не только возвращает index.html, но и отправляет клиенту index.js, index.css и т.д. Наиболее интуитивно понятным преимуществом является то, что браузеру не нужно анализировать страницу, а затем инициировать запрос на получение данных, что экономит время загрузки страницы.
Хотя есть небольшая разница, все равно выглядит довольно интересно и стоит попробовать. Ведь это мелко на бумаге, и я точно знаю, что это дело надо делать!
HTTP/2
Я раньше не использовал HTTP/2, поэтому всегда полезно знать, прежде чем приступать к практике. По поводу HTTP/2 тоже много информации в интернете, кратко расскажу о его самых больших преимуществах:быстро!. Скорость здесь сравнивается с HTTP 1.x, так почему же она быстрее?
сжатие заголовка
Заголовки здесь относятся к заголовкам HTTP-запроса. Вы можете задаться вопросом, насколько большим может быть заголовок запроса, и это ничто по сравнению с ресурсом. На самом деле, с развитием Интернета в заголовке запроса передается все больше и больше данных, и появляется длинный список случайных «агентов пользователя». Кроме того, файлы cookie также будут хранить все больше и больше информации. Что более раздражает, так это то, что все запросы к странице будут приводить эти повторяющиеся данные заголовка запроса.
Итак, HTTP/2 принимаетАлгоритм HPACK, что может значительно сжать данные заголовка и уменьшить общий размер запроса ресурсов. Общий принцип заключается в поддержке двух словарей, одного статического словаря и более распространенных имен заголовков. Динамический словарь, который поддерживает общие данные заголовков для разных запросов.
мультиплексирование
Мы знаем, что в HTTP 1.x мы можем запрашивать параллельно. Однако у браузера есть верхний предел одновременных запросов для одного и того же доменного имени (FireFox, Chrome имеет ограничение 6). Поэтому многие веб-сайты могут иметь несколько статических ресурсных станций. И каждый раз, когда делается запрос, соединение TCP должно быть восстановлено.Большинство веб-инженеров, должно быть, поняли трехстороннее рукопожатие TCP, и цена этого рукопожатия относительно высока.
Хотя в http1.x есть поддержка активности, чтобы избежать трехэтапного рукопожатия TCP, поддержка активности является последовательной. Таким образом, несколько параллельных рукопожатий или отсутствие последовательного рукопожатия — не лучший результат.Мы надеемся, что параллельного рукопожатия нет.
К счастью, HTTP/2 решает эту проблему. Когда клиент и сервер установят соединение, обе стороны установят двусторонний канал потока. Канал потока может содержать несколько сообщений одновременно (http-запрос), разные сообщения в каждом кадре потока данных могут передаваться параллельно не по порядку, а перегрузки не будут влиять друг на друга, тем самым достигается TCP-соединение, N одновременно выполняемых http спроси. Увеличьте параллелизм за счет снижения накладных расходов TCP-соединений, скорость HTTP / 2 была значительно улучшена, особенно в относительно высокой задержке в сети.
Вот сравнительная диаграмма двух каскадных потоков времени сетевых запросов:
HTTP 1.1
HTTP/2
Server Push
Выше мы описали, что соединение HTTP/2 устанавливает двунаправленный канал потоковой передачи. Server Push означает, что в определенном потоке он может возвращать данные, которые клиент активно не запрашивает.
Вышеупомянутое сжатие и мультиплексирование заголовков не требует от разработчиков никаких действий, пока включен HTTP/2, браузер также его поддерживает. Но Server Push требует от разработчиков написания кода для работы. Тогда давайте начнем и поиграем с Node.
Узел HTTP/2 Сервер Push в действии
Поддержка узлом HTTP/2
Начиная с Node 8.4.0 существует экспериментальная поддержка HTTP/2. Вечером 24 апреля 2018 года Node v10 наконец-то был выпущен, но для HTTP/2 это все еще экспериментальная поддержка. . . Однако сообщество экспериментировало с удалением HTTP/2.ОбсуждатьЧто ж, я считаю, что в ближайшем будущем Node сможет улучшить поддержку HTTP/2. Поэтому перед этим мы можем освоить эти знания и немного попрактиковаться.
По тыквенной росписи
Мы первая базаДокументация узла, чтобы создать службу HTTP/2. Здесь следует упомянуть, что ни один из современных популярных браузеров не поддерживает незашифрованный, небезопасный HTTP/2. Итак, мы должны сгенерировать сертификат и ключ, а затем передатьhttp2.createSecureServer
Создавайте безопасные ссылки HTTP/2.
Студенты, которые хотят попрактиковаться и получить местные сертификаты, могут обратиться сюда:портал.
// server.js
const http2 = require('http2')
const fs = require('fs')
const streamHandle = require('./streamHandle/sample')
const options = {
key: fs.readFileSync('./ryans-key.pem'),
cert: fs.readFileSync('./ryans-cert.pem'),
}
const server = http2.createSecureServer(options)
server.on('stream', streamHandle)
server.listen(8125)
Затем мы следуем документации, пишем обработку потока и отправляем данные, URL-адрес которых равен «/».
// streamHandle/sample.js
module.exports = stream => {
stream.respond({ ':status': 200 })
stream.pushStream({ ':path': '/' }, (err, pushStream, headers) => {
if (err) throw err
pushStream.respond({ ':status': 200 })
pushStream.end('some pushed data')
pushStream.on('close', () => console.log('close'))
})
stream.end('some data')
}
Затем мы открываем хром, заходим на https://127.0.0.1:8125 и обнаруживаем, что страница всегда отображаетсяsome data.some pushed dataЯ не знаю, где эти активно продвигаемые данные. Откройте панель Network Requests и других запросов тоже нет.
Я не могу понять, но я не хочу останавливаться на достигнутом, что мне делать?
схватить от куклы
Сначала я решил написать обычный бизнес-запрос HTTP/2, код выглядит следующим образом:
module.exports = (stream, headers) => {
const path = headers[':path']
if (path.indexOf('api') >= 0) {
// 请求api
stream.respond({ 'content-type': 'application/json', ':status': 200 })
stream.end(JSON.stringify({ success: true }))
} else if (path.indexOf('static') >= 0) {
// 请求静态资源
const fileType = path.split('.').pop()
const contentType = fileType === 'js' ? 'application/javascript' : 'text/css'
stream.respondWithFile(`./src${path}`, {
'content-Type': contentType
})
} else {
// 请求html
stream.respondWithFile('./src/index.html')
}
}
Основная идея кода состоит в том, чтобы судить о ссылке запроса, когда адрес запроса имеетapi
Когда используется слово, возвращается json, когда адрес запроса содержитstatic
, возвращаются статические ресурсы соответствующего пути. В противном случае вернитеhtml
документ.
Содержимое html-файла:
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>HTTP/2 Server Push</title>
<link rel="shortcut icon" type=image/ico href=/static/favorite.ico>
<link href=/static/css/app.css rel=stylesheet>
</head>
<body>
<h1>HTTP/2 Server Push</h1>
<script type=text/javascript src=/static/js/test.js></script>
</body>
</html>
После запуска мы откроем хром, посетим https://127.0.0.1:8125, мы увидим обычную отрисовку страницы, увидим панель «Сеть», также было обнаружено, что соглашение — HTTP/2.
Итак, мы разработали очень простое приложение HTTP/2. Далее добавляем функцию server push, при доступеindex.html
запросы, мы активноjs
Ресурс возвращается, чтобы увидеть, как отвечает браузер.
поймать тыкву
module.exports = (stream, headers) => {
const path = headers[':path']
if (path.indexOf('api') >= 0) {
// 请求api部分代码-略
} else if (path.indexOf('static') >= 0) {
// 请求静态资源部分代码-略
} else {
// 请求html时 主动推送js文件
stream.pushStream(
{ ':path': '/static/js/test.js' },
(err, pushStream, headers) => {
if (err) throw err
pushStream.respondWithFile('./src/static/js/test2.js', {
'content-type:': 'application/javascript'
})
pushStream.on('error', console.error)
}
)
stream.respondWithFile('./src/index.html')
}
}
Основная идея кода заключается в том, что когда клиент запрашиваетindex.html
, сервер в дополнение к возвращениюindex.html
файл, кстатиtest2.js
Этот файл отправляется на сервер, если клиент снова запрашиваетhttps://127.0.0.1:8125/static/js/test.js
, вы сразу получитеtest2.js
.
Здесь я используюtest2.js
Цель состоит в том, чтобы удобно знать, что то, что запрашивает клиент, передается сервером.test2.js
Файл получается повторным запросом непосредственно с сервера.test.js
документ.
вtest.js
Будет напечатано на странице: это обычный js.test2.js
Будет напечатано на странице: Это сервер push js.
Как и ожидалось, это должно быть последнее. Затем мы открываем Chrome и посещаем https://127.0.0.1:8125, показывая следующие результаты:
! ! ! ! Переверни стол! ! ! !
Ожидается, что этот результат отображения не будет распечатанThis is server push js
, запрошенные страницы js файлы или обычные сетевые запросы, а не я беру на себя инициативу проталкиватьtest2.js
. Я искал по всей стране и за границей и, наконец, увидел похожую проблему под выпуском Node:http2 pushStream not providing files for :path entries (CHROME 65, works in FF) .
Работает в FireFox ???????Ошибка Chrome ??????
Пишешь код по документу, а результат не такой как в документе.Различные расследования бесполезны.В итоге выясняется,что есть какие-то не субъективные причины.Самая большая боль программистов в этом.. .. Потом я смешался с болью и разбитым сердцем.. С переменой настроения я открыл свой собственный Firefox, зашел на страницу и показал следующие результаты:
На этот раз, наконец, правильно! Как видите, то, что напечатано на странице,test2.js
вывод файла.
Сначала было бесполезно рисовать совок, но это тоже из-за бага в Хроме. Несмотря ни на что, мы сделали огромный шаг вперед.
ps: моя версия Chrome 66.0.3359.117, и эта ошибка все еще есть.
безвкусный
Хотя мы сделали большой шаг вперед, мы столкнулись с неприятной проблемой: больше наших статических ресурсов размещалось на cdn. Тогда наш реальный сценарий столкнется со следующей ситуацией:
- Все ресурсы сайта, включая html/css/js/image и т. д., находятся на одном бизнес-сервере. Извините одноклассники, пропускная способность вашего бизнес-сервера изначально низкая, и я боюсь, что он не выдержит столько одновременных запросов на статические ресурсы, а вы безнадежно тормозите.
- Сетевая маршрутизация идет на бэкенд, то есть HTML идет на бэкенд, а остальные статические ресурсы размещаются на cdn. Извините одноклассники, все статические ресурсы лежат на cdn, как вы пропихиваете свой бизнес-сервер?
- Полное разделение фронтенда и бекенда, html и другие статические ресурсы находятся на cdn. В данном случае это все еще полезно, но не очень эффективно. Поскольку HTTP/2 сам по себе поддерживает мультиплексирование, потребление сети, вызванное трехэтапным рукопожатием TCP, было снижено. Нажатие на сервер просто замедляет время, необходимое браузеру для разбора html, что слишком тривиально для современных браузеров. (ps: Как раз когда я писал статью, я случайно увидел, что провайдер облачных услуг поддерживает серверную прошивку.)
Скажем так, это кусок дерьма! В конце концов, бамбуковая корзина пуста?
Рожден быть полезным
Нелегко отказаться от лечения по-человечески. Подумав еще раз, есть еще некоторые сценарии приложений — инициализированные запросы API.
В настоящее время многие одностраничные приложения часто имеют множество запросов инициализации для получения информации о пользователе, получения данных страницы и т.д. И все это требует загрузки html, затем загрузки js, а затем выполнения. И часто эти данные не загружаются, и страница может отображаться только пустой. Однако ресурсы js одностраничных приложений часто очень велики. Также обычно размер пакета поставщика составляет несколько мегабайт. Ожидание, пока браузер загрузит и проанализирует такой большой пакет, может занять много времени. В настоящее время запросите некоторые API инициализации. Если эти API отнимают много времени, страница долгое время будет пустой.
Но если мы можем отправить инициализированные данные API клиенту при запросе html, при анализе js и последующем запросе, мы можем получить данные немедленно, что может сэкономить драгоценное время на белом экране. Просто сделай это, давай сделаем это снова!
module.exports = (stream, headers) => {
const path = headers[':path']
if (path.indexOf('api') >= 0) {
// 请求api
stream.respond({ 'content-type': 'application/json', ':status': 200 })
stream.end(JSON.stringify({ apiType: 'normal' }))
} else if (path.indexOf('static') >= 0) {
// 请求静态资源代码-略
} else {
// 请求html
stream.pushStream({ ':path': '/api/getData' }, (err, pushStream, headers) => {
if (err) throw err
pushStream.respond({ ':status': 200 , 'content-type': 'application/json'});
pushStream.end(JSON.stringify({ apiType: 'server push' }))
});
stream.respondWithFile('./src/index.html')
}
}
Точно так же я сделал некоторые различия между API-интерфейсом обычного запроса и данными API, отправленными сервером, чтобы более интуитивно судить о том, были ли получены данные, отправленные сервером. Затем напишите следующий запрос в файле внешнего интерфейса js и распечатайте результат запроса:
window.fetch('/api/getData').then(result => result.json()).then(rs => {
console.log('fetch:', rs)
})
К сожалению, мы получили следующие результаты:
Результат запроса указывает, что это не данные, отправленные сервером. Кусок денег растёт мудростью, это очередная ошибка браузера? или этоfetch
Не поддерживает получение данных с сервера? Я тут же написал другую версию, используя XMLHttpRequest:
window.fetch('/api/getData').then(result => result.json()).then(rs => {
console.log('fetch:', rs)
})
const request = new XMLHttpRequest();
request.open('GET', '/api/getData', true)
request.onload = function(result) {
console.log('ajax:', JSON.parse(this.responseText))
};
request.send();
Результат выглядит следующим образом:
! ! ! ! Переверни стол! ! ! !
Это правдаfetch
Push сервер http2 не поддерживается!
все еще безвкусный
На самом деле, кромеfetch
В дополнение к неподдержке существует более фатальная проблема, то есть этот серверный толчок на сервере текущего узла не может выполнять нечеткое сопоставление по URL-адресу ресурса, отправленного сервером. То есть, если запрос имеет динамические параметры URL, он не будет совпадать. как в моем примереstream.pushStream({ ':path': '/api/getData' }, pushHandle)
, если интерфейс, запрошенный внешним интерфейсом,/api/getData?param=1
, то данные серверного пуша не будут получены.
Кроме того, он поддерживает только запросы на получение запросов и руководителей, пост и поставку также не поддерживаются.
противfetch
По этому вопросу я снова искал по стране и за границей, но не мог найти причину. Это также показывает, что в настоящее время в сообществе редко используется функция push-уведомлений сервера.
Поэтому кажется, что в push API сценарий его применения снова ограничен, и он применим только к начальному GET-запросу для отправки фиксированного URL.
Море горечи бескрайнее, к берегу повернув спиной
Подводя итог, я пришел к выводу, что в настоящее время на Node использование server push неуместно в экстремальных ситуациях и вероятностях, а затраты превышают выгоду. В основном по следующим причинам:
- Начиная с Node v10.0.0, HTTP/2 все еще является экспериментальным модулем;
- Браузерная поддержка очень плохая, как, например, упомянутая выше ошибка Chrome, fetch не поддерживает отправку на сервер;
- Реальных сценариев для загрузки статических ресурсов очень мало, и улучшение скорости в теории не будет очевидным;
- Push API поддерживает только фиксированные URL-адреса и не может содержать никаких динамических параметров.
Примечание. Приведенный выше контент ограничен только службами Node, а другие серверы сами по себе не изучались, поэтому вышеуказанных проблем может и не быть.
Хотя я думаю, что в настоящее время использовать push-уведомление с сервера непросто, HTTP/2 по-прежнему хорош.В дополнение к преимуществам, упомянутым в начале этой статьи, HTTP/2 имеет много новых и полезных функций, таких как приоритет потока, управление потоком и т. д. Некоторые функции не рассматриваются в этой статье. Каждый может узнать об этом, это определенно должно сильно помочь нам в разработке высокопроизводительного веба в будущем!
Исходный код, задействованный в этой статье: https://github.com/wuomzfx/http2-test.
Оригинальная ссылка: https://yuque.com/wuomzfx/article/eh551s