BigPipe - это технология, разработанная Facebook для оптимизации скорости загрузки веб -страницы. В Интернете практически нет статей, введенных в node.js. На самом деле, это не просто node.js. Реализации BigPipe на других языках редки в Интернете. После того, как эта технология появилась, я подумал, что после того, как вся структура веб -страницы была отправлена в первую очередь, я использовал еще один или несколько запросов AJAX, чтобы запросить модули на странице. Лишь недавно я узнал, что основная концепция BigPipe состоит в том, чтобы использовать только один HTTP -запрос, но элементы страницы отправляются в порядке.
Это будет легко понять эту основную концепцию. Благодаря асинхронной особенности Node.js легко реализовать BigPipe с Node.js. В этой статье будут использовать примеры шаг за шагом, чтобы проиллюстрировать причины технологии BigPipe и простую реализацию, основанную на Node.js.
Я буду использовать Express, чтобы продемонстрировать. Для простоты мы выбираем Джейд в качестве шаблонного двигателя, и мы не используем функцию подключения двигателя (частичный), а вместо этого используем шаблон ребенка, чтобы отобразить HTML в качестве данных родительского шаблона.
Сначала создайте папку Nodejs-Bigpipe и напишите файл package.json следующим образом:
Кода -копия выглядит следующим образом:
{
«Имя»: «Биг-труба-эксперимент»
, "Версия": "0.1.0"
, "private": true
, "зависимости": {
"Экспресс": "3.xx"
, "Консолидат": "Последний"
, "Джейд": "Последний"
}
}
Запустите NPM Install, чтобы установить эти три библиотеки. Консолидат используется для облегчения вызова Джейд.
Давайте сначала сделаем простейшую попытку, два файла:
app.js:
Кода -копия выглядит следующим образом:
var Express = require ('Express')
, минусы = require ('consolidate')
, Jade = require ('нефрит')
, path = require ('path')
var app = express ()
app.Engine ('Jade', Cons.Jade)
app.set ('views', path.join (__ dirname, 'views'))
app.set ('view Engine', 'Jade')
app.use (function (req, res) {
res.render ('mayout', {
S1: «Здравствуйте, я первый раздел».
, S2: «Здравствуйте, я второй раздел».
})
})
app.listen (3000)
Просмотры/макет.jade
Кода -копия выглядит следующим образом:
Doctype html
голова
Название привет, мир!
стиль
раздел {
Маржа: 20px Auto;
Граница: 1px пунктир с серой;
Ширина: 80%;
Высота: 150px;
}
Раздел#S1! = S1
Раздел#S2! = S2
Эффекты следующие:
Далее мы поместили два шаблона раздела в два разных файла шаблонов:
Просмотры/S1.jade:
Кода -копия выглядит следующим образом:
H1 Частичный 1
.content! = Контент
Просмотры/S2.Jade:
Кода -копия выглядит следующим образом:
H1 Частичный 2
.content! = Контент
Добавьте несколько стилей в макет. Jade Style
Кода -копия выглядит следующим образом:
Раздел H1 {
размер шрифта: 1,5;
Заполнение: 10px 20px;
поля: 0;
пограничный под кносом: 1PX-точка серого;
}
раздел div {
Маржа: 10px;
}
Изменить app.use () часть app.js на:
Кода -копия выглядит следующим образом:
var temp = {
S1: jade.compile (fs.readfilesync (path.join (__ dirname, 'views', 's1.jade')))))))
, S2: jade.compile (fs.readfilesync (path.join (__ dirname, 'views', 's2.jade')))))))
}
app.use (function (req, res) {
res.render ('mayout', {
S1: temp.s1 ({content: «Привет, я первый раздел.»})
, s2: temp.s2 ({content: «Привет, я второй раздел».})
})
})
Мы говорили ранее: «HTML после рендеринга завершается шаблоном ребенка в качестве данных родительского шаблона», что означает, что два метода Temp.s1 и Temp.s2 будут генерировать код HTML для двух файлов S1.jade и S2.jade, а затем используют эти два часа кода в качестве значений двух переменных S1 и S2 в Layout.
Теперь страница выглядит так:
Вообще говоря, данные двух разделов получаются отдельно - будь то запрашивая базу данных или запрос RESTFUL, мы используем две функции для моделирования, таких как асинхронные операции.
Кода -копия выглядит следующим образом:
var getData = {
D1: function (fn) {
SetTimeout (fn, 3000, null, {content: «Привет, я первый раздел».})
}
, d2: function (fn) {
SetTimeout (fn, 5000, null, {content: «Привет, я второй раздел».})
}
}
Таким образом, логика в app.use () будет более сложной, и самый простой способ справиться с ней:
Кода -копия выглядит следующим образом:
app.use (function (req, res) {
getData.d1 (function (err, s1data) {
getData.d2 (function (err, s2data) {
res.render ('mayout', {
S1: Temp.s1 (S1Data)
, S2: Temp.s2 (S2Data)
})
})
})
})
Это также получит желаемые результаты, но в этом случае потребуется полные 8 секунд, чтобы вернуться.
Фактически, логика реализации показывает, что getData.d2 начинает звонить после возврата getData.d1, и у них нет такой зависимости. Мы можем использовать такие библиотеки, как Async, которые обрабатывают асинхронные вызовы JavaScript для решения этой проблемы, но давайте просто напишем ее здесь:
Кода -копия выглядит следующим образом:
app.use (function (req, res) {
var n = 2
, result = {}
getData.d1 (function (err, s1data) {
result.s1data = s1data
--N || writeresult ()
})
getData.d2 (function (err, s2data) {
result.s2data = s2data
--N || writeresult ()
})
Function writerSult () {
res.render ('mayout', {
S1: temp.s1 (result.s1data)
, S2: temp.s2 (result.s2data)
})
}
})
Это занимает всего 5 секунд.
Перед следующей оптимизацией мы добавляем библиотеку jQuery и помещаем стиль CSS во внешние файлы. Кстати, мы добавим файл runtime.js, необходимый для использования нефритового шаблона, который мы будем использовать позже, и запустим его в каталоге, содержащем app.js:
Кода -копия выглядит следующим образом:
Mkdir Static
CD Статический
curl http://code.jquery.com/jquery-1.8.3.min.js -o jquery.js
ln -s ../node_modules/jade/runtime.min.js jade.js
И выньте код в теге в стиле в Mayout.jade и поместите его в static/style.css и измените тег на головке:
Кода -копия выглядит следующим образом:
голова
Название привет, мир!
Ссылка (href = "/static/style.css", rel = "stylesheet")
Скрипт (src = "/static/jquery.js")
Скрипт (src = "/static/jade.js")
В app.js мы имитируем обе скорости загрузки до двух секунд и добавили перед app.use (function (req, res) {:
Кода -копия выглядит следующим образом:
var static = express.static (path.join (__ dirname, 'static'))
app.use ('/static', function (req, res, next) {
SetTimeout (Static, 2000, REQ, Res, Next)
})
Из -за внешних статических файлов наша страница теперь имеет время загрузки около 7 секунд.
Если мы вернем часть головки, как только получим HTTP -запрос, а затем два секции ожидают, пока асинхронная операция не будет завершена до возврата, в этом используется механизм кодирования блокировки передачи HTTP. В node.js, если вы используете метод res.write (), передача: кусочек заголовок будет автоматически добавлен. Таким образом, в то время как браузер загружает статический файл, сервер узлов ждет результата асинхронного вызова. Давайте сначала удалите две строки секции в макете. Яд:
Кода -копия выглядит следующим образом:
Раздел#S1! = S1
Раздел#S2! = S2
Следовательно, нам не нужно давать этот объект в res.render () {s1:…, s2:…}, и потому что res.render () будет называть res.end () по умолчанию, нам нужно вручную установить функцию обратного вызова после завершения рендеринга и использовать метод res.write () в нем. Содержание Mayout.jade не должно быть в функции обратного вызова prisheresult (). Мы можем вернуть его, когда получим этот запрос. Обратите внимание, что мы вручную добавили заголовок типа контента:
Кода -копия выглядит следующим образом:
app.use (function (req, res) {
res.render ('layout', function (err, str) {
if (err) return res.req.next (err)
res.setheader ('content-type', 'text/html; charset = utf-8')
res.write (str)
})
var n = 2
getData.d1 (function (err, s1data) {
res.write ('<section id = "s1">' + temp.s1 (s1data) + '</section>')
--N || res.end ()
})
getData.d2 (function (err, s2data) {
res.write ('<section id = "s2">' + temp.s2 (s2data) + '</section>')
--N || res.end ()
})
})
Теперь конечная скорость загрузки вернулась примерно до 5 секунд. В фактической работе браузер сначала получает код детали головки и загружает три статических файла. Это занимает две секунды. Затем в третьей секунде появляется частичный 1, частичный 2 появляется в пятой секунде, а загрузка веб -страницы заканчивается. Я не буду уделять скриншот, эффект скриншота такой же, как и скриншоты за предыдущие 5 секунд.
Тем не менее, важно отметить, что этот эффект может быть достигнут, потому что getData.d1 быстрее, чем getData.d2. То есть какой блокировка на веб -странице возвращается в первую очередь, зависит от того, кто возвращает результат асинхронного вызова интерфейса, стоящего за ней. Если мы изменим getData.d1, чтобы вернуться через 8 секунд, мы сначала вернем частично.
Эта проблема в конечном итоге приводит нас к Bigpipe, которая является технологией, которая может отделить порядок отображения каждой части веб -страницы от порядок передачи данных.
Основная идея состоит в том, чтобы сначала передавать общую структуру всей веб -страницы, а части, которые необходимо передавать позже, представлены пустыми divs (или другими тегами):
Кода -копия выглядит следующим образом:
res.render ('layout', function (err, str) {
if (err) return res.req.next (err)
res.setheader ('content-type', 'text/html; charset = utf-8')
res.write (str)
res.write ('<section id = "s1"> </section> <section id = "s2"> </section>')
})
Затем запишите возвращенные данные в JavaScript
Кода -копия выглядит следующим образом:
getData.d1 (function (err, s1data) {
res.write ('<cript> $ ("#s1"). html ("' + temp.s1 (s1data) .replace (/"/g, '// "') + '") </script>')
--N || res.end ()
})
Обработка S2 аналогична этому. В настоящее время вы увидите, что во второй секунду запрашивая веб -страницу, появляются два пустых пунктирных ящика, в пятой секунду появляются частичные 2, а в восьмой секунду появляется частичный 1, и запрос на веб -страницу завершен.
На этом этапе мы завершили простейшую веб -страницу, реализованную технологией BigPipe.
Следует отметить, что, если записанный фрагмент веб -страницы имеет теги сценария, такие как изменение S1.jade на:
Кода -копия выглядит следующим образом:
H1 Частичный 1
.content! = Контент
сценарий
предупреждение («Блюдо от S1.jade»)
Затем обновите веб -страницу, и вы обнаружите, что предложение о предупреждении не выполнено, а веб -страница будет иметь ошибки. Проверьте исходный код и знайте, что это ошибка, вызванная строкой в <script>. Просто замените его на <// script>
Кода -копия выглядит следующим образом:
res.write ('<script> $ ("#s1"). html ("' + temp.s1 (s1data) .Replace (/"/g, '//"').replace(/</script>/g,' <// script> ') +' ") </script> ')
Выше мы объясняем принципы Bigpipe и основной метод реализации BigPipe с Node.js. И как это следует использовать в реальности? Вот простой метод броска кирпичей и нефрита, код заключается в следующем:
Кода -копия выглядит следующим образом:
var resproto = require ('Express/lib/response')
resproto.pipe = function (селектор, html, заменить) {
this.write ('<cript>' + '$ ("' + selector + '").' +
(Заменить === true? 'replywith': 'html') +
'("' + html.replace (/"/g, '//"').replace(/</script>/g,' <// script> ') +
'") </script>')
}
Функция Pipename (res, name) {
res.pipecount = res.pipecount || 0
res.pipemap = res.pipemap || {}
if (res.pipemap [name]) return
res.pipecount ++
res.pipemap [name] = this.id = ['pipe', math.random (). toString (). substring (2), (new Date ()). valueof ()]. join ('_')
this.res = res
this.name = имя
}
resproto.pipename = function (name) {
вернуть новое трубопровод (это, имя)
}
resproto.pipelayout = function (view, options) {
var res = это
Object.keys (options) .foreach (function (key) {
if (options [key] exanceof pipename) Options [key] = '<span id = "' + options [key] .id + '"> </span>'
})
res.render (view, options, function (err, str) {
if (err) return res.req.next (err)
res.setheader ('content-type', 'text/html; charset = utf-8')
res.write (str)
if (! res.pipecount) res.end ()
})
}
resproto.pipepartial = function (имя, просмотр, параметры) {
var res = это
res.render (view, options, function (err, str) {
if (err) return res.req.next (err)
res.pipe ('#'+res.pipemap [name], str, true)
-res.pipecount || res.end ()
})
}
app.get ('/', function (req, res) {
res.pipelayout ('mayout', {
S1: res.pipename ('s1name')
, S2: res.pipename ('s2name')
})
getData.d1 (function (err, s1data) {
res.pipepartial ('s1name', 's1', s1data)
})
getData.d2 (function (err, s2data) {
res.pipepartial ('s2name', 's2', s2data)
})
})
Также добавьте два раздела в Layout.jade:
Кода -копия выглядит следующим образом:
Раздел#S1! = S1
Раздел#S2! = S2
Идея здесь заключается в том, что содержимое трубы должно быть сначала размещено с помощью тега SPAN, асинхронно получить данные и отображать соответствующий HTML -код, прежде чем вывести его в браузер, и заменить элемент Span Placeholder на метод jQuery.
Код этой статьи находится по адресу https://github.com/undozen/bigpipe-onnode. Я сделал каждый шаг в коммит. Я надеюсь, что вы действительно можете запустить его на местном уровне и взломать его. Поскольку следующие несколько шагов включают в себя заказ на загрузку, вам действительно нужно открыть браузер самостоятельно, чтобы испытать его и не можете увидеть его на скриншоте (на самом деле, он должен быть реализован с помощью GIF -анимации, но мне было слишком лениво это).
Есть еще много возможностей для оптимизации в области практики Bigpipe. Например, лучше всего установить запускаемое время для содержания трубы. Если данные, вызванные асинхронно, возвращаются быстро, вам не нужно использовать BigPipe. Вы можете напрямую генерировать веб -страницу и отправить ее. Вы можете подождать, пока запрос на данные не превысит определенный период времени перед использованием BigPipe. По сравнению с AJAX, использование BigPipe не только сохраняет количество запросов из браузера на сервер Node.js, но и сохраняет количество запросов с сервера Node.js на источник данных. Тем не менее, давайте поделимся конкретными методами оптимизации и практики после того, как сеть Snowball использует BigPipe.