Node js server side javascript работает. Подключение Серверного JavaScript. Зачем это нужно

Рисунок 2.1 Архитектура среды клиент-серверного приложения на языке JavaScript

Три слоя - это:

  • WWW-клиенты (такие как Netscape Navigator-клиенты): Этот слой предоставляет приложению межплатформенный интерфейс конечного пользователя. Этот слой может также содержать некоторую логику приложения, такую как правила проверки данных, реализованные в клиентском JavaScript. Клиенты могут находиться внутри или за пределами прокси-сервера корпоративной сети.
  • Netscape WWW-сервер/БД клиент: Этот слой состоит из Netscape-сервера с включённым JavaScript. Он содержит логику приложения, обслуживает безопасность и контролирует доступ к приложению нескольких пользователей, используя серверный JavaScript. Этот слой позволяет клиентам как в пределах действия, так и за пределами прокси-сервера иметь доступ к приложению. WWW -сервер также работает как клиент с любым установленным сервером БД.
  • Серверы баз данных: Этот слой состоит из SQL-серверов БД, работающих обычно на высокопроизводительных рабочих станциях. Он содержит все данные БД, метаданные и правила ссылочной целостности/referential integrity, необходимые для работы приложения. Этот слой обычно находится в зоне действия прокси-сервера корпоративной сети и может предоставлять слой безопасности дополнительно к слою безопасности WWW -сервера. Netscape Enterprise Server поддерживает использование серверов БД: ODBC, DB2, Informix, Oracle и Sybase. Netscape FastTrack Server поддерживает только ODBC. Дополнительно о LiveWire Database Service см. Часть 3, "Служба LiveWire Database Service."

Клиентская среда JavaScript работает как часть WWW -клиентов, а серверная среда JavaScript работает как часть Netscape web-сервера с доступом к одному или более серверов БД. показывает более детально, как серверная среда JavaScript и приложения, созданные для неё, встраиваются в Netscape web-сервер.

Системные Требования

Чтобы разрабатывать приложения JavaScript, использующие преимущества и клиентского, и серверного JavaScript, Вам нужна подходящая среда для разработки и публикации. В целом рекомендуется разрабатывать приложения на системе, отделённой от сервера публикации, поскольку разработка потребляет много ресурсов (порты соединений, пропускная способность, время процессора и память). При разработке также может быть нарушена работа уже опубликованных приложений конечного пользователя.

Среда разработки JavaScript состоит из:

  • Утилит для авторизации и компиляции приложений JavaScript. Эти утилиты обычно находятся на машине разработчика.
  • Машины разработчика с web-сервером для запуска приложений JavaScript, находящихся в стадии разработки.
  • Машины публикации с web-сервером для публикации разработанных приложений. Конечные пользователи осуществляют доступ к приложениям, находящимся на этом сервере.

Необходимые утилиты:

  • Браузер с возможностью выполнения JavaScript, такой как Netscape Navigator, входящий в состав Netscape Communicator.
  • Компилятор приложений JavaScript, такой как компилятор web-серверов Netscape.
  • Редактор, такой как Emacs или Notepad.

Публикация и машины публикации требуют наличия следующего программного обеспечения:

  • Web-сервера;
  • Машины выполнения JavaScript, такой как машина web-серверов Netscape.
  • Возможности конфигурирования Вашего сервера для работы приложений JavaScript, как это сделано в JavaScript Application Manager, поставляемом вместе с web-серверами Netscape.

Кроме того, если ваше приложение использует JavaScript-службу LiveWire Database Service, Вам понадобится:

  • Программа - сервер реляционных БД на Вашей машине-сервере БД. См. документацию вашего сервера БД. В некоторых случаях Вам понадобится установить web-сервер и сервер БД на одной машине. О специфических требованиях серверного JavaScript см. Главу 10, "Конфигурирование Базы Данных."
  • Клиент БД и сетевое программное обеспечение на машине Вашего web-сервера. Если Вы используете одну машину и как сервер БД, и как web-сервер, типичное клиентское обеспечение БД как правило уже установлено при установке сервера БД. В противном случае Вам нужно удостовериться, что клиент БД установлен на той же машине, что и web-сервер, чтобы можно было иметь доступ к БД как клиент. О требованиях к клиентскому программному обеспечению см. дополнительно документацию поставщика БД.
Информация Конфигурации

В этом разделе рассматривается информация конфигурации для использования серверного JavaScript. Дополнительно о настройке БД для работы с сервисом LiveWire Database Service см. Главу 10, "Конфигурирование Базы Данных."

Подключение Серверного JavaScript

Чтобы запускать приложения JavaScript на Вашем сервере, Вы обязаны подключить машину выполнения JavaScript в вашем Server Manager, щёлкнув Programs, а затем выбрав серверный JavaScript. После появления промпта "Activate the JavaScript application environment/Активизировать среду приложений JavaScript ?" выберите Yes и щёлкните OK. У Вас спросят также об ограничении доступа к Application Manager. Дополнительно см.

ПРИМЕЧАНИЕ: Если Вы не подключите машину выполнения JavaScript, приложения JavaScript не смогут запускаться на этом сервере.

Чтобы использовать и сервлеты, и LiveWire, Вам необходимо подключить серверный JavaScript до подключения Java. Оба могут быть подключены через использование меню программ Administration Server"а. Если Вы модифицируете путь к классам/classpath в obj.conf , Ваши изменения будут утеряны, если Вы подключите/отключите серверный JavaScript или Java из программного меню Administration Server"а. Альтернативой редактирования директивы classpath в obj.conf является установка переменной окружения CLASSPATH в Unix или установка переменной CLASSPATH в установках System в Windows NT. Если Вам нужно редактировать obj.conf непосредственно, сохраните первоначальный файл на всякий случай. В Enterprise Server 4.0 Вы должны добавить CLASSPATH info в файлы конфигурации JVM (jvm12.conf для Solaris и NT) через интерфейс Enterprise Administration Server.

Как только Вы активируете среду приложений JavaScript, Вы обязаны остановить и рестартовать Ваш web-сервер, чтобы ассоциированные переменные окружения начали действовать. Если этого не сделать, приложения JavaScript, использующие службу LiveWire Database Service, работать не будут.

Защита Application Manager"а

Application Manager предоставляет контроль над приложениями JavaScript. В связи с его особыми возможностями Вы должны защитить его от неавторизованного доступа. Если Вы не ограничиваете доступ к Application Manager"у, любой может добавлять, удалять, изменять, стартовать и останавливать приложения на Вашем сервере. Это, естественно, может привести к нежелательным последствиям.

Вы (разработчик приложений на JavaScript) должны иметь права доступа для использования Application Manager"а на сервере разработчика, так как Вы используете его для работы с приложением при разработке. Администратор Вашего web-сервера, однако, может не предоставить Вам таких прав на сервере разработчика.

Если Вы подключите машину выполнения JavaScript в Server Manager"е, промпт спросит Вас, ограничивать ли доступ к Application Manager"у. Выберите Yes и щёлкните OK. (Yes - по умолчанию.) После этого любой, кто попытается получить доступ к Application Manager"у, обязан будет ввести имя пользователя и пароль Server Manager"а, чтобы получить возможность использовать Application Manager и приложение-образец dbadmin . Дополнительно см. руководство администратора для Вашего web-сервера.

Если Ваш сервер не использует Secure Sockets Layer (SSL), имя пользователя и пароль для Application Manager"а передаются по сети в некодированном виде. Перехватив эти данные, можно получить доступ к Application Manager"у. Если Вы используете тот же самый пароль для Вашего сервера администратора, хакер получит также контроль и над этим сервером. Следовательно, можно рекомендовать не использовать Application Manager вне прокси-сервера, если Вы не используете SSL. О том, как подключить SSL к серверу, см. справочник администратора Вашего web-сервера.

На сервере (IIS) по следующим причинам:

Передача навыков - мы хотели бы использовать JavaScript и jQuery на сервере и не использовать, например, VB Script./классический asp. .Net framework/Java и т. Д. Исключается из-за этого.

Улучшены параметры поиска/доступности. Мы хотели бы иметь возможность использовать jQuery в качестве системы шаблонов, но это небезопасно для поисковых систем и пользователей с отключенным - если мы не можем выборочно запускать этот код на сервере.

В IIS и Windows Server есть значительные инвестиции, поэтому изменение не является вариантом.

Я знаю, что вы можете запускать jScript в IIS с помощью хоста Windows Script, но я не уверен в масштабируемости и процессе, связанном с этим. Я также не уверен, что это будет иметь доступ к DOM.

Вот диаграмма, которая, надеюсь, объясняет ситуацию. Мне было интересно, если кто-нибудь сделал что-нибудь подобное?

EDIT: я не ищу критика в веб-архитектуре, я просто хочу знать, есть ли какие-либо возможности для манипулирования DOM страницы, прежде чем она будет отправлена ​​клиенту, используя javascript. Jaxer - один из таких продуктов (без IIS). Спасибо.

5 7 ответы

Идея повторного использования клиентского JS на сервере может казаться соблазнительной, но я не уверен, что сам jQuery будет готов к запуску в серверной среде.

Вам нужно будет определить глобальный контекст для jQuery каким-то образом, инициализируя окно, документ, имя пользователя, местоположение и т. Д. . Я не уверен, что это выполнимо.

Кроме того, как отметил Cheeso, Active Server Pages - очень устаревшая технология, в начале века она была заменена на ASP.Net Microsoft. Раньше я использовал систему устаревания с использованием ASP 3.0 более года, и это было больно. Самое замечательное времяпрепровождение - отладка: вы вряд ли найдете что-нибудь для этой цели сегодня и должны будете ослабить красивые ошибки, как в журнале IIS:

ошибка "800a9c68"
Определенная пользователем или объектная ошибка

Тем не менее, я могу подтвердить, что мне удалось повторно использовать клиентский и серверный JScript. Но это был код, написанный мной, который знал, что он будет использоваться на сервере.

JScript работает в IIS с помощью ASP. Активные серверные страницы.
Он был впервые доступен в 1996 году.

В конце концов ASP.NET был представлен как преемник. Но ASP по-прежнему поддерживается.

Однако для DOM-страницы нет DOM.

Возможно, вам придется немного пересмотреть свою архитектуру.

Я думаю, что единственные жизнеспособные решения, которые вы, вероятно, найдете в любом месте рядом с готовыми к работе, включают в себя размещение IIS перед Java. Есть две браузерные среды, которые я знаю о закодированных для Java:

Если вы хотите что-то чистое-IIS/MS, я думаю, что ваше наблюдение за хостом WindowsScript и/или чем-то вроде полузаброшенного JScript.NET, вероятно, примерно так же близко, как и вы, а также порт (который вы, вероятно, придется начинать) с чем-то вроде Env-js или HTMLUnit.

Кроме того, я не знаю, видели ли вы список решений на стороне сервера в Википедии:

Наконец... вы, вероятно, могли бы написать пригодную для использования jQuery-подобную библиотеку на любом языке, на котором уже есть какая-то библиотека DOM и первоклассные функции (или, если это невозможно, eval). См. Например, pQuery для Perl (http://metacpan.org/pod/pQuery). Это даст вам преимущества стиля jQuery для манипулирования документами. Передача навыков велика, и у JavaScript есть замечательное слияние очень приятных функций, но, с другой стороны, разработчики, которые заботятся о том, чтобы изучать несколько языков, также великолепны, а - не единственный приятный язык.

Что именно вы подразумеваете под

от RisingStack . Переведено с разрешения правообладателей.

В этой главе я расскажу вам о том, как вы можете запустить простой HTTP-сервер на Node.js и начать обрабатывать запросы.

Модуль http для вашего Node.js-сервера

Когда вы начинаете создавать HTTP-приложения в Node.js, встроенные модули http/https - это то, с чем вы будете взаимодействовать.

Давайте создадим ваш первый HTTP-сервер на Node.js! Нам будет нужно подключить модуль http и привязать наш сервер к порту 3000 .

// содежимое index.js
const http = require("http")
const port = 3000 const requestHandler = (request, response) => {
console.log(request.url)
response.end("Hello Node.js Server!")
} const server = http.createServer(requestHandler) server.listen(port, (err) => {
if (err) {

})

Затем запускаем этот скрипт:

$ node index.js

Что нужно здесь отметить:

  • requestHandler: эта функция будет вызываться каждый раз, когда на сервер придёт запрос. Если вы откроете в своём браузере адрес localhost:3000 , два сообщения появятся в консоли: одно для / и одно для favicon.ico .
  • if (err) : обработка ошибок: если порт уже занят или есть какие-то другие причины, по которым сервер не может быть запущен, мы получим уведомление об этом.

Модуль http крайне низкоуровневый: создание сложного веб-приложения с использованием вышеприведенного фрагмента кода очень трудоемко. Именно по этой причине мы обычно выбираем фреймворки для работы над нашими проектами. Есть множество фреймворков, вот самые популярные:

  • express
  • hapi
  • koa
  • restify

В этой и следующих главах мы будем использовать Express, так как именно для него вы можете найти множество модулей в NPM.

ExpressБыстрый, гибкий, минималистичный веб-фреймворк для Node.js -  http://expressjs.com/

Добавление Express в ваш проект - это просто установка через NPM:

$ npm install express --save

После того, как вы установили Express, давайте посмотрим, как создать приложение аналогичное тому, что мы написали ранее:


const app = express()
const port = 3000 app.get(‘/’, (request, response) => {
response.send("Hello from Express!")
}) app.listen(port, (err) => {
if (err) {
return console.log("something bad happened", err)
} console.log(`server is listening on ${port}`)
})

Самое большое различие, которое вы можете здесь заметить, заключается в том, что Express по умолчанию даёт вам роутер. Вам не нужно вручную разбирать URL, чтобы решить, что делать, вместо этого вы определяете маршрутизацию приложения с помощью app.get , app.post , app.put и так далее, а они уже транслируются в соответствующие HTTP-запросы.

Одна из самых мощных концепций, которую реализует Express - это паттерн Middleware.

Middleware - промежуточный обработчик

Вы можете думать о промежуточных обработчиках как о конвейерах Unix, но для HTTP-запросов.

На диаграмме вы можете увидеть, как запрос идёт через условное Express-приложение. Он проходит через три промежуточных обработчика. Каждый обработчик может изменить этот запрос, а затем, основываясь на вашей бизнес-логике, третий middleware отправит ответ, либо запрос попадёт в обработчик соответствующего роута.

На практике вы можете сделать это следующим образом:

Const express = require("express")
const app = express() app.use((request, response, next) => {
console.log(request.headers)
next()
}) app.use((request, response, next) => {
request.chance = Math.random()
next()
}) app.get("/", (request, response) => {
response.json({
chance: request.chance
})
}) app.listen(3000)

Что следует здесь отметить:

  • app.use: это то, как вы можете описать middleware. Этот метод принимает функцию с тремя параметрами, первый из которых является запросом, второй - ответом, а третий - коллбеком next . Вызов next сигнализирует Express о том, что он может переходить к следующему промежуточному обработчику.
  • Первый промежуточный обработчик только логирует заголовки и мгновенно вызывает следующий.
  • Второй добавляет дополнительное свойство к запросу - это одна из самых мощных функций шаблона middleware. Ваши промежуточные обработчики могут добавлять дополнительные данные к объекту запроса, который могут считывать/изменять middleware, расположенные ниже.
Обработка ошибок

Как и во всех фреймворках, правильная обработка ошибок имеет решающее значение. В Express вы должны создать специальный промежуточный обработчик - middleware с четырьмя входными параметрами:

Const express = require(‘express’)
const app = express() app.get(‘/’, (request, response) => {
throw new Error(‘oops’)
}) app.use((err, request, response, next) => {
// логирование ошибки, пока просто console.log
console.log(err)
response.status(500).send(‘Something broke!’)
}) app.listen(3000)

Что следует здесь отметить:

  • Обработчик ошибок должен быть последней функцией, добавленной с помощью app.use .
  • Обработчик ошибок принимает коллбек next . Он может использоваться для объединения нескольких обработчиков ошибок.
Рендеринг HTML

Ранее мы рассмотрели, как отправлять JSON-ответы. Пришло время узнать, как отрендерить HTML простым способом. Для этого мы собираемся использовать пакет handlebars с обёрткой express-handlebars .

Сначала создадим следующую структуру каталогов:

├── index.js
└── views
├── home.hbs
└── layouts
└── main.hbs

После этого заполните index.js следующим кодом:

// index.js
const path = require("path")
const express = require("express")
const exphbs = require("express-handlebars")
const app = express() app.engine(".hbs", exphbs({
defaultLayout: "main",
extname: ".hbs",
layoutsDir: path.join(__dirname, "views/layouts")
}))
app.set("view engine", ".hbs")
app.set("views", path.join(__dirname, "views"))
app.listen(3000)

Приведенный выше код инициализирует движок handlebars и устанавливает каталог шаблонов в views/layouts . Это каталог, в котором будут храниться ваши шаблоны.

После того, как вы сделали эту настройку, вы можете поместить свой начальный html в main.hbs . Чтобы всё было проще, давайте сразу перейдём к этому:



Express handlebars


{{{body}}}

Вы можете заметить метку {{{body}}}  - здесь будет размещен ваш контент. Давайте создадим home.hbs !

Hello {{name}}

Последнее, что мы должны сделать, чтобы заставить всё это работать, - добавить обработчик маршрута в наше приложение Express:

App.get("/", (request, response) => {
response.render("home", {
name: "John"
})
})

Метод render принимает два параметра:

  • Первый - это имя шаблона.
  • Второй - данные, необходимые для рендеринга.

Как только вы сделаете запрос по этому адресу, вы получите что-то вроде этого:



Express handlebars


Hello John

Это всего лишь верхушка айсберга. Чтобы узнать, как добавить больше шаблонов (и даже частичных), обратитесь к официальной документации express-handlebars .

Отладка Express

В некоторых случаях вам может потребоваться выяснить, что происходит с Express, когда приложение работает. Для этого вы можете передать следующую переменную окружения в Express: DEBUG=express* .

Вы должны запустить свой Node.js HTTP-сервер, используя:

$ DEBUG=express* node index.js

Резюме

Вот как вы можете настроить свой первый HTTP-сервер на Node.js с нуля. Я рекомендую Express для начала, а затем поэкспериментируйте.

Крэйга Батлера, "Станет ли когда-нибудь server-side Javascript модным среди разработчиков", лишний раз убеждаешься, что люди воспринимают JavaScript исключительно однотипно. Большинство девелоперов рассматривает его сугубо в контексте браузера. Во многом потому, что существует определённая путаница между самим языком и DOM’ом браузера. DOM интерфейс - это, собственно, и есть то место, где JavaScript девелоперы проводят большую часть своего рабочего времени. Там же можно найти подтверждение ещё одному факту - многие люди терпеть не могут JavaScript. Хотя я всё равно уверен, что те люди, которые ценят лучшие качества JavaScript и закрывают при этом глаза на его недостатки, вряд ли удивлены тому, что JavaScript собирается потихоньку перебираться на выполнение сервером. Скорее всего они еще и рады этому. И, поверьте, это только начало!

Итак, где же серверный JavaScript можно найти сейчас? Jaxer – это фреймворк и сервер для разработки веб приложений на JavaScript. Серверный API не страдает от недостатка предоставляемых возможностей, включая доступ к файлам, базам данных и сетевым сокетам. Граница между применением серверных и пользовательских скриптов достаточно размыта, к примеру, с помощью тех же серверных скриптов можно манипулировать DOM’ом веб-странички. Такие скрипты придают вашему HTML коду толику ASP-стиля. var nme = document.createTextNode("Hello my name is Jaxer."); var para = document.getElementById("name"); para.appendChild(nme); В данном случае у атрибута Runat может быть три значения – server, both , и server-proxy . Если назначить значение server, скрипт выполняется ещё до отправки страницы браузеру. Иначе его обработка происходит уже на клиентской стороне. Если установлено значение server-proxy, функция может быть вызвана из client-side скриптов по имени, но будет проксироваться с помощью Ajax на свой server-side эквивалент. Helma - это еще один фреймворк для написания JavaScript скриптов, выполняемых на стороне сервера. Helma имеет шаблонную систему, которая помогает избежать смешивания server-side и client-side кода. Здесь, вместо того, чтобы прописывать действия JavaScript, которые затем рендерят темплейт, вы вводите данные в сам процесс рендеринга. Каждый HTTP запрос, таким образом, инициирует выполнение сконфигурированного действия. Вот пример шаблона, или, если использовать Helma-жаргон, "скина", названный "hello": А вот action, которые рендерит его: res.data.name = "Helma"; this.renderSkin("hello"); Судя по теме в Википедии, есть немало других примеров “серверного” JavaScript . Почти во всех из них используется Rhino или SpiderMonkey для выполнения скриптов. Серверный JavaScript. Насколько это всё серьёзно? Реализации выполнения скриптов JavaScript на сервере, как видим, уже начинают появляться, но, в любом случае, по популярности им, так или иначе, очень далеко до повсеместного использования PHP. Отметим, что это вполне справедливо, ведь server-side JavaScript в настоящее время ограничен своим фреймворком. Такие JavaScript скрипты, написанные в одной среде вряд ли могут быть портированы из-за отсутствия стандартного API. Впрочем работа над этой проблемой уже началась в рамках ServerJS Group . Да и проекты типа jslibs также должны посодействовать её решению. Отсутствие подходящего хостинга также является острым вопросом, хотя Jaxer и AppJet и представляют свои собственные хостинг-платформы, а Helma приложения можно разместить на любых поддерживающих Java хостинг-решениях. После того, как проблема создания стандартной библиотеки будет решена, я уверен, мы увидим, что хостинг-поддержка будет улучшаться и расширяться и вы сами будете требовать у вашего хостера "mod_javascript" поддержку. Наконец, JavaScript имеет более чем достаточно шероховатостей и недостатков, заставляющих многих разработчиков сомневаться о целесообразности его использовании в качестве server-side решения. Но я думаю, что выход ECMAScript 3.1 должен серьёзно поколебать их уверенность в этом. Нетрудно заметить, что поддержка JavaScript стала появляться во многих платформах – и веб и десктопных, локальных и серверных. Будет ли серверная JavaScript поддержка предлагаться в хостинговых планах также повсеместно, как и PHP? Я думаю, это неизбежно.

Андрей Сумин : Добрый день, меня зовут Андрей Сумин, я работаю в компании Mail.Ru. Есть немного лишнего времени, поэтому я подготовил небольшой бонус - как раз на оставшиеся двадцать минут. Я покажу вам возможности нашего шаблонизатора, который мы сделали, чтобы достичь тех цифр, которые нам нужны.

До этого у нас был шаблонизатор полностью на "Си". Он был довольно своеобразный. Поэтому мы очень хотели конструкций вроде таких, когда можно использовать JavaScript.

Ниже вы видите то, во что json.name превращается на сервере.

Я не буду сейчас заострять внимание на этом. В основной части доклала я расскажу про то, во что мы компилируем, зачем и почему. Просто сейчас я даю основные конструкции, которыми можно вывести значение из хеша. Можно как-то развлекаться, делая "escape" этого значения.

Safe=true. Ниже вы видите компилированный код. По "safe=true" сразу видно, что, допустим, у нас исчез "try catch".

Конструкции, которые нужны любому шаблонизатору - это "if" и, соответственно, "choose". Но обычно используется форма "if else", очень нужная разработчику. Но если вы хотите валидный XML, то, к сожалению, единственный способ - это "choose".

Здесь показан код, в который он компилируется.

Естественно, используются циклы. Куда же без циклов? Цикл по массиву, цикл по хэшу.

Еще наш шаблонизатор по умолчанию "тримит" все, что находится между тэгами.

Боюсь, что здесь могут присутствовать те, кому эти вещи не знакомы. Но в html пробел - это значащий символ. Поэтому желательно указывать, что вы хотите в этом месте иметь пробел. Да, есть такие специфические вещи.

Fest:script нужен, чтобы мы прямо посреди шаблонизатора могли исполнить какой-нибудь JavaScript-код.

Сейчас я попробую сделать небольшие демонстрации, чтобы вы видели, к чему это приводит. Вот, у меня есть вчерашняя заготовка. Это обычный HTTP-сервер на Node.js.

(Докладчик показывает демонстрацию.)

Он запускает некоторый сервер. Отсюда мы берем шаблон. Шаблон я, естественно, покажу. У меня тут некоторые демонстрации закомментированы. Берем шаблон, он должен превратиться в JavaScript-функцию "template". Вот здесь мы тысячу раз этот шаблон применяем. Так как основная тема моего доклада - это все-таки цифры, то нам интересно измерить его производительность в каких-то стрессовых ситуациях. Ниже, соответственно, мы выводим результаты для этого шаблона.

Давайте посмотрим повнимательнее. Первый шаблон - это цикл в десять тысяч итераций.

Давайте здесь поставим лучше html, так будет интересней. Он выводит span, внутри которого мы выводим значение "hello" из хэша входного в шаблон и занимаемся конкатенацией с текущей датой. Конкатенация строк - достаточно "дорогая" операция. Это все пройдет десять тысяч раз. Плюс я хочу вам еще показать, что и сам шаблон выполнится тысячу раз.

Давайте попробуем запустить...

Сейчас будут цифры на основе Node.js. V8 используется Node.js. Мы видим десять тысяч вот таких конкатенаций. Не волнуйтесь, я вывожу в браузер только последние, иначе у меня были бы проблемы с памятью в браузере. 10 тысяч конкатенаций выполнены тысячу раз. Они нам дали пять секунд. Это означает, что на один раз пришлось 10 тысяч конкатенаций, и это заняло у нас 5 миллисекунд на Node.js на сервере.

Можем посмотреть, сколько времени это займет в браузере (если мы этот же шаблон отправим прямо в браузер).

Цифра чуть-чуть отличается. Можем, на самом деле, еще посмотреть другой браузер. Нас же интересует JavaScript-шаблонизатор. В общем, вы уже видите, что получится. Это Opera. Значит, он должен иметь одни и те же вещи на клиенте и на сервере. Там почти минута - 40 секунд, по-моему. В любом случае, даже если здесь будет очень большая цифра, ее нужно делить на тысячу.

Вот, пожалуйста. 39 секунд. Видимо, остальное время он потратил на рендеринг в самом браузере. Даже когда у нас в разных браузерах получаются цифры, различающиеся в несколько раз (может быть, даже на порядок) - вы все равно учитываете, что все это надо делить на тысячу. Это означает, что на один проход ушло 39 миллисекунд.

Чтобы шаблонизировать, мы этот шаблонизатор используем на клиенте для проектов, которые используются, - это могут быть мобильные версии проектов, для старых версий Android, и у нас никаких проблем с производительностью нет. В IE я, к сожалению, сейчас не могу показать, потому что у меня все-таки Mac. Но есть некоторые интересные нюансы.

У нас шаблонизатор по умолчанию со включенным "escaped". Об этом будет речь в основной части доклада. Если мы знаем, что наши данные точно не введены пользователем, то "escaped" можно выключить.

Давайте посмотрим, что произойдет с цифрами. Это сейчас на сервере. Это V8 на сервере через Node.js.

Так, видимо, я где-то ошибся. Вот здесь "save true". Это не должно влиять. А, ну, да, конечно - сервер-то надо перезапустить. Я все-таки привык с клиентом работать, где нажимаешь Ctrl+S+F5.

Вот, собственно, разница.

Очень много времени требует "html escape". Это я взял, чтобы продемонстрировать порядок проблем, с которыми приходится сталкиваться.

Что касается "save", на предыдущем слайде каждое JS-выражение, которое делает верстальщик, по умолчанию оборачивается в "try catch".

Это обходится, на самом деле, не очень дорого, по крайней мере, в V8.

Разницы особо никакой. Хотя, на самом деле, в "try catch" было обернуто, как вы понимаете, тысяча на десять тысяч. В общем, операций очень много - 10 миллионов. В современных браузерах это выполняется очень быстро, и в современных движках тоже.

Если у вас есть пожелания, чем его нагрузить, то можете высказать. Давайте попробуем. Может, мы даже найдем что-нибудь, что можно улучшить.

Теперь второй интересный пример, без которого мы не смогли жить. Он не новый. Мы его позаимствовали в каком-то виде из XSLT. Тоже об этом буду говорить. Сейчас - демонстрация. Все-таки, возможно, кто-то придет к началу доклада.

У нас есть отдельный шаблон, который выводит span. Внутри span у нас есть вызов (вот, я его выделил) - fest:get. Означает, что в этом месте вывести блок с именем "word". А ниже идет определение того, что это содержимое того блока, соответственно, и равно слово "word".

Вот. Мы выводим содержимое этого блока. По шаблону видно, что просто включается в этот файл, в котором сказано вывести span с этим блоком. Больше ничего не происходит, потому что "if" у нас возвращает "ложь" (по крайней мере, условия в "if" возвращают "ложь").

Понятно, что поменялось? Это для примера, что в браузере, собственно, эти же шаблоны точно также отрабатывают. Вот это то самое наследование, которое есть во всех современных шаблонизаторах (и не очень современных). Очень близко, по-моему, к "Django" - там практически то же самое.

Я хочу сказать, что вот этот XML-синтаксис - это "syntactic sugar" для верстальщиков. У нас некоторые люди, которые знакомы с XSL, когда видели этот синтаксис, начинали писать на нем как на родном. Но это приводило к проблемам. Вот, у нас есть "fest value", которое я вот здесь вывожу. Вот оно, допустим - "fest value".

Они приходили и спрашивали, есть ли у него атрибут "формат", чтобы можно было сразу в "fest value" это как-то сформатировать. На что я им отвечал: "Вот в этом конкретно пункте забудьте про XSL. У вас есть внутри JavaScript". В том числе, про этот же самый синтаксис. Он сделан исключительно для того, чтобы рядовые задачи приводили к очень быстрым решениям по скорости. Поэтому никаких особых "наворотов" там нет. Если "навороты" есть, это должно быть осознанное решение конкретного разработчика. Оно должно быть выражено в JavaScript.

Вот поинтереснее задача. Допустим, у нас есть JavaScript-библиотека… Вот эта функция умеет склонять слова. На входе она получает число и массив слов, которые надо склонять. Мы в этой строчке ее подключаем. Значит, на сервере в этот момент этот JavaScript выполнится, создаст эту функцию.

Создаем массив слов. Вот небольшой массив, который выведет результат от нуля до девяти, как эта функция себя будет вести с такими входными параметрами.

Вот функция слова просклоняла.

Само по себе это не очень интересно, если бы не следующий пример.

Теперь она - шаблон, который генерирует html для браузера. Внимание: здесь уже не "fest include", а "fest insert". В этом месте вставляем этот JavaScript. Браузер его получит именно как JavaScript-программу.

Вот получаем ровно тот же пример. Вот она наша функция пришла на клиент, и мы можем ее использовать на клиенте.

Самый большой бонус, ради которого мы бились - чтобы одни и те же библиотеки, шаблоны, программы на JavaScript могли использоваться на клиенте и на сервере. Раньше это было невозможно как раз потому, что было медленно и непонятно. Вдруг кто помнит первые попытки использовать JavaScript на сервере - это был, по-моему, какой-то IDE. Jagser, по-моему, он назывался.

Реплика из зала : Это Aptana?

Андрей Сумин : Да, Aptana. Там был, конечно, полный провал. Я его честно попробовал, честно старался, но сдался. С появлением отдельных движков от разных браузеров, с появлением конкуренции, когда они соревнуются друг с другом по производительности, мы получили возможность использовать JavaScript на сервере.

У вас есть вопросы по моим примерам?

Реплика из зала : Можно посмотреть скомпилированный код?

Андрей Сумин : Да. Скомпилированный код можем посмотреть. Вот, скомпилированный код шаблона. Тут некоторые служебные функции идут сначала, конечно же. Как я говорил, нужен escape.

Вот, допустим, наш цикл. Вот я его выделил. Давайте попробую приблизить. Вот тот самый hello+Date. Как я и обещал, у нас все по умолчанию в "try catch". Так что если вдруг вам попался верстальщик, который не очень соображает в JavaScript, он, по крайней мере, вам ничего не сломает. Escape html - по-честному, все без обмана.

Реплика из зала : А у вас в команде есть кто-то, кто разбирается в JavaScript?

Андрей Сумин : Да. У меня в команде есть больше, наверное, чем три или четыре (может, даже чем пять людей), которые умеют "готовить" java script. Они понимают, что это такое, и как его "готовить". Гораздо большая опасность - это такая архитектурная "травма" JavaScript - так называемое слово "var". Если вы его не объявили, то у вас будут проблемы. На сервере это фактически утечка памяти, потому что по умолчанию переменная попадет в список глобальных и там и останется, по крайней мере, до перезагрузки контекста.

Но у нас есть Straight mode, который не позволяет этого сделать. Текущий шаблонизатор по умолчанию работает со Straight mode. Я пытался вызвать утечки памяти - у меня не получилось.

Начнем рассказ про выполнение JavaScript на сервере.

Зачем это нужно?

Естественно, первый вопрос, который возникает, когда мы говорим про JavaScript на сервере - это вопрос "зачем?". Я очень много программировал на JavaScript , я его очень люблю. Но этого недостаточно. Надо все-таки заниматься делом.

В больших компаниях (я работал в нескольких больших компаниях) есть такая особенность. У нас есть очень много сильных backend-разработчиков. Они умеют писать код, много знают про серверы. Поэтому пускай они пишут шаблонизатор для верстальщиков (если не требуется взять какой-то особенный). Из-за этого возникает куча проблем. У этих разработчиков свои проблемы, свое начальство, им надо что-то делать. Когда к ним приходили с просьбами типа "дайте нам что-нибудь доделать, нам чего-то не хватает", мне это все жутко не нравилось.

При этом есть вторая особенность, особенно актуальная сейчас. Есть очень много людей, которые знают JavaScript. Конкретно у меня очень много людей, которые знают JavaScript. А текущий шаблонизатор у меня на "Си", но у меня в подчинении нет ни одного человека, который знает "Си".

Конечно, как я говорил, шаблонизация на клиенте. Обо всем этом мы задумались, начиная с проекта "Почта". Проект "Почта" не может обходиться без шаблонизации на стороне сервера, потому что нужен быстрый старт. Конкуренты не спят. Он не может обходиться без шаблонизации на стороне клиента, потому что применяется Ajax, тут тоже все должно быть быстро. Пользователь должен быть удовлетворен, потому что очень легко перейти к конкурентам.

При этом у нас получалась такая ситуация, когда у нас есть шаблонизация на клиенте и шаблонизация на сервере, логика одна и та же, а шаблоны разные. Дублирование кода, куча проблем. Непонятно, что с этим делать. Реально очень много ошибок.

Чего же мы хотели?

Как я уже говорил, помимо JavaScript, я еще очень много писал на XSL. Это тоже очень хороший шаблонизатор, возможно, самый мощный. Но тоже не без "родовых травм". Хотя какие-то его возможности нужны.

Плюс, если у нас есть XSL, один из его недостатков - это то, что в него не встроен какой-то алгоритмически полный язык. Когда мы хотим пересортировать там массив - это еще решаемая задача. Но когда мы хотим там пройтись "регуляркой", в XSL это настоящий ад.

Поэтому мы захотели лучшего от этих двух вещей?

Вот пример, который я вам показывал, он очень похож на "Django". Мы объявляем некоторый блок. Вот здесь у нас есть "title". Его содержимое сразу же определяется - что это "Mail.ru". Если мы подключаем этот шаблон как есть на странице, то у нас выводится "title" с заголовком "Mail.ru". Все проекты, которые есть на Mail.ru, могут его подключить и иметь единый заголовок.

Но у нас проявляется проект "Почта". Естественно, мы хотим все то же самое, что в остальных проектах, только заголовок другой. Не писать же из-за этого другой шаблон! Хочется как раз его кусок и переопределить.

Сам JavaScript.

Нам очень хочется, чтобы с входными данными можно было работать алгоритмически полным языком. Вот есть такая конструкция, как "fest script". В ней можно что-то поделать именно на JavaScript, а ниже через "fest value", собственно, это вынести.

Вот этот шаблон выведет в Mail.ru.

Сам шаблонизатор.

На самом деле, у нас было очень много споров по поводу XML-синтаксиса. XML-синтаксис избыточен - с этим глупо спорить. Однако наша основная задача состояла в том, чтобы научиться "готовить" JavaScript на сервере. Поэтому не хотелось параллельно решать еще и задачи "давайте мы придумаем в нем синтаксис", "давайте мы в нем придумаем эскейпинг", "давайте мы в нем напишем всякие плагины к IDE". Не хотелось этого решать. Поэтому мы взяли XML, получились с короткой поддержкой IDE.

Любой уважающий себя IDE без всяких настроек вам скажет, что у вас XML не валиден. А валидность XML шаблонов автоматом нам, получается, отдает валидность выходного html. Вот валидация из коробки.

Плюс там еще во всех IDE есть подсветка, автоматическая табуляция. Не хотелось заморачиваться.

Еще в XML есть по умолчанию такая хорошая вещь, как пространства имен. Это расширяемость.

У нас есть шаблонизатор, он не очень-то много умеет. У вас есть реальные проекты. Вдруг вам стала нужна мультиязычность. "Зашивать" ее в fest-шаблон по умолчанию - это как-то странно. Он "разбухнет" и перестанет поддерживаться. А если вы объявляете, допустим, пространство имен своего проекта, то вы можете "перехватить" в компиляторе это событие и обработать это по-своему. Допустим, у вас могут быть специфичные пространства имен fest и Mail.ru.

За многие годы у нас появилась куча инструментов работы с XML. Когда я занимался компиляцией из fest-шаблона в XML, у меня был богатый выбор того, чем можно воспользоваться. В результате я воспользовался SAX-парсером, но не факт, что на этом остановлюсь.

Преобразование XML to XML

Опять же, я упоминал, что у нас выходные строки не очень простые. XML - это достаточно "развесистый" язык. Поэтому заниматься "эскейпингом" не очень хотелось. Когда у вас управляющие конструкции такие же, как в XSL, у вас практически все проблемы "эскейпинга" исчезают. Плюс еще есть дополнительные вещи, как CDATA, которые позволяют "эскейпить" тоже.

Реализация

Когда мы определились, как должен выглядеть шаблон (примерно поняли, что нам нужно для тестового стенда, чтобы он начал работать), мы приступили к реализации самого компилятора XML в JavaScript.

Это был довольно необычный для меня опыт парного программирования. Я даже сам был уверен в том, что парное программирование дает очень хорошие результаты, сильно лучшие, чем одиночное.

Но конкретно на этой задаче произошло обратное. Мы взяли шаблон, поняли, какой хотим результат, и начали компилировать. Я делал свою версию, а Костя делал свою версию. Мы раз в неделю собирались и, грубо говоря, замеряли цифры. Правда, его цифры были все время чуть-чуть быстрее.

Структура против функции

Мы выбрали два принципиально разных подхода. Я решил компилировать XML в структуру. А Костя компилировал сразу в функцию. Мне это поначалу показалось не очень безопасным.

Чтобы вы понимали, компиляция в структуру - это примерно такой массив.

Хеши означают действие, а строки можно либо вывести сразу к клиенту, либо что-то с ними сделать.

Для пояснения. Первый хеш - "action":"template" означает, что начинается шаблон. Со второй строкой ничего делать не надо, ее можно прямо так вывести к клиенту. Третья строчка означает, что четвертую строку надо пропустить через val , и результат val уже вывести к клиенту.

Или, например (наверное, так будет понятнее), рассмотрим вариант с "if".

Первые, вторые строчки - то же самое, что на предыдущем слайде. Третья строка означает, что следующее выражение надо выполнить. Если оно является истинным, то, соответственно, нужно вывести "true". Если оно является ложным, то надо вывести "false".

На самом деле, это навеяно уже довольно известным приемом, который используется чуть ли не во всех калькуляторах - это обратная пользовательская запись. Я хотел с ней как-то поиграть, но все это оказалось не нужным. Реальные шаблоны оказались гораздо проще.

С реализацией в функцию все понятно. Просто по каким-то условиям начинает конкатенироваться изначальная переменная, которая выставлена в пустую строку.

Результат мы хотели получить вот такой.

Это один из наших главных проектов. Естественно, думая о шаблонизаторе, надо было думать об этом проекте. Список писем с достаточным количеством папок.

Скажу честно: первая реализация, которую я сделал, решала эту задачу за 200 миллисекунд.

Мы с Костей "бодались", по-моему, в течение месяца или полутора месяцев. Приходили после выходных, и один говорил: "А у меня 180!" Второй отвечал: "А у меня 150!" И так далее, и тому подобное. На самом деле, в какой-то момент я сдался, потому что понял, что уже не догоню. Мы начали делать реализацию с функцией, она победила. Когда мы все-таки "вылизали" все до конца, у нас на эту задачу уходило 3 миллисекунды.

Список писем рисовался за 3 миллисекунды. Трансформация примерно такая, это максимально близко к самым простым конструкциям JavaScript. "For" - в "for", "if" - в "if". Choose - это "if {} else".

Нам пришлось немного помучиться с fest:set, поскольку я понимал, что нам нельзя дальше жить без этого переопределения. Оно делается тоже не очень сложно. У нас по мере первого выполнения функции шаблона сначала создается как раз объект "set". По мере того, как ему попадаются XML-блоки "set", переписывается функция, которая, собственно, переопределяет содержимое этого блока.

Поэтому, если "set" много, то при отрабатывании шаблона, который вернет XML, выполнятся только последние из них, а не все. Это тоже дало небольшой прирост.

Очень интересный эффект дало следующее. С первой реализацией мы не очень сильно заморачивались. Когда было 200 миллисекунд - мы, в общем, этого не видели. Но компилировали мы сначала в такую структуру. У нас есть исходный HTML, который компилировался просто в конкатенацию строчка за строчкой.

Когда мы подбирались к трем миллисекундам, мы нехитрой "регуляркой" в скомпилированном шаблоне заменили эти "плюс-равно" на одну строку - мы получили, на самом деле, приличную цифру - плюс 30 % производительности.

Еще раз поговорим о безопасности.

Безопасность

Под безопасностью я понимаю не только XSS: нам обязательно нужно, чтобы шаблонизатор минимизировал его "из коробки". Причем я надеюсь, что он их исключает "из коробки". Но "try catch" должен быть, если вдруг нерадивый верстальщик возьмет свойства без определения (англ. undefined).

Поэтому по умолчанию все в try catch. Тем более, по нашим тестам это "бесплатно".

Это escape. Там есть и escape JavaScript, escape HTML. Пользовательские данные, чтобы не было доступа.

Опять же, как мы видели, это самая "дорогая" операция в нашем шаблонизаторе.

Конечно, используем "strict mode", чтобы не было утечек памяти.

Если верстальщик вдруг что-то увидит, то он сразу в разработке заметит, что что-то сделал не так. А на продакшне мы начнем перезапускать серверы и между перезапусками выяснять, что же, собственно, произошло.

Как я уже говорил, компиляция по умолчанию - это как раз "try catch" плюс "escape".


Интеграция

Мы получили цифры, которые хотели. Или, по крайней мере, получили какие-то приличные цифры, с которыми не стыдно прийти.

Следующий интересный момент. У нас в компании, как минимум, есть вот эти языки. Я уверен, что их сильно больше. Но это те, с которым я сталкивался.

Это "Си" - у нас HTTP-сервер на "Си", и на нем много логики написано. Perl: большая часть почты написана на Perl. Какие-то проекты пишутся на Python. Node.js у нас на продакшне нет, но мы его тоже используем в разработке, естественно, с ним надо жить.

Казалось бы, когда мы добавляем к этому многообразию еще и V8, то мы сделаем только хуже. Но когда на части проектов мы добавили V8 ко всем этим технологиям… На самом деле, для это несложно. Нужно, чтобы V8 (там есть Google API) опрокинула вот эти три функции для логирования.

V8 - это просто библиотека, сама по себе она ничего не умеет. Это библиотека, которая принимает что-то на вход, что-то дает на выход. Естественно, когда у вас внутри что-то происходит, вы хотите об этом сообщать внешнему миру. Этот самый fest_log - уже на усмотрение технологии. Он либо в консоль, либо в браузер, либо письмом, либо СМС-кой системному администратору может сообщать, что внутри V8 что-то не так. А V8 эту функцию внутри получает и может как-то взаимодействовать с ней и через нее с внешним миром.

Fest_file, естественно, шаблонизация. Она бьется на файлы, "include", "insert" и так далее, и тому подобное. Поэтому когда V8 собирает и компилирует файлы, ей нужно обрабатывать "include" и "insert". Поэтому внешняя среда должна предоставить V8 возможность прочитать файл.

Dirname - это такая мелочь. Когда вы хотите прочитать файл, вам нужно на что-то опереться. Это директория текущего исполняемого JavaScript-файла. Вот этих трех конструкций достаточно, чтобы V8 заработала.

Произошло следующее. У нас на сервере back-end целый зоопарк технологий. Но есть проект, который на сервере использует fest. Есть проект, который на клиенте использует fest. Есть проект, который на клиенте и на сервере использует fest. Везде единый синтаксис, и верстальщики мигрируют между проектами, никаких проблем с этим нет. Конечно, мы "из коробки" получили интеграцию с браузером.

Fest компилируется в примитивные JavaScript-конструкции, которые (я уверен) принимаются даже Explorer 5. Просто негде было попробовать, извините.

Работа с реальными пользователями

Итак, у нас есть реализация, и нам все-таки хочется добраться до пользователей, узнать, как все заработает у них. Я с этой реализацией пришел к Игорю и сказал, что у нас тестово-нагрузочные прогоны укладываются в три миллисекунды. Он ответил: "Пляши, как хочешь - а там на все четыре".

Вы должны понимать, что продакшн - это, помимо трансформации, еще какие-то данные, которые надо получить, что-то с ними делать. А 4 миллисекунды - это на все.

Тут использовалась уже наша внутренняя кухня, вряд ли такое у кого-нибудь есть. Но, к сожалению, она оказалась очень важна, чтобы ее упустить.

Наш HTTP-сервер написан на "Си", мы его называем Light. Когда он получает данные от сервера back-end, то он хранит их в плоском хэше. На самом деле, это примитивный вариант. Он должен легко читаться. На всякий случай уточню, что это список писем. Первая строчка означает, что его длина равна пяти, вторая строчка означает, что у первого письма заголовок "letter", а третья строка означает, что оно не прочтенное.

Естественно, когда мы говорим о JavaScript, то хотим видеть внутри него вот такой V8.

Каким-то образом нам нужно это засунуть внутрь V8. Мы пробовали очень много вариантов. Пересмотрели всякие Binding, PerlOuI и свои решения - по-моему, около двух недель мучились. Вот вариант, который мы точно попробовали - это решение проблемы через V8 API.

Я пробовал, но, к сожалению, не получилось привести код на "Си". Его реально очень много. Я попробую объяснить на словах.

Через V8 API можно было сделать так: если вы в JavaScript пишете JSON, в этот момент V8 обращается к вашему коду на "Си". Код на "Си" в этот момент может вернуть то, что он считает нужным. Он может написать JSON.name. Это будет в два подхода - первый раз он обратится за JSON, а второй - за name. Это очень долго.

Второй вариант - это JSON.parse. Когда вы поднимаете контекcт V8, то на "Си" вы собираете строчку, которая один в один похожа на JSON. Отправляете ее в контекст, и прямо в контексте накладываете на это V8 JSON.parse "из коробки". Соответственно, уже внутри контекста у вас есть хеш, который можно отправлять в шаблон. Это тоже оказалось медленно.

Самый быстрый вариант - это как раз третий вариант. Я попробую все-таки кусок кода на "Си" от него показать.

Вот у вас есть вторая строчка. Это как раз шаблон. Та JavaScript функция, в которую компилируется fest. Она на вход ждет JSON. Вы собираете такую строчку, вызов этой функции, и первый и единственный ее аргумент - это тот JSON, который надо использовать. Даете команду выполнить это.

Цифры не очень были. При ограничении в четыре миллисекунды, две из которых - это трансформация. Мы их замеряли отдельно. Четыре секунды занимала подготовка данных.

Вы сейчас смотрите: казалось бы, четыре миллисекунды на подготовку данных - ну и что? А это 67 % от трансформации.

67 % времени у нас занимает подготовка данных для генерации этого html. У нас была задача уложиться в те же мощности, что сейчас есть. 6 миллисекунд вместо 4, это близко. Но все-таки было обидно. На самом деле, в этот момент мы почти забросили эту идею.

Но я все-таки нашел в себе силы взять тот хеш, который вы помните. Я взял его в текстовом виде, пришел домой. Честно говоря, сейчас не помню код. Но я из текстового вида его как-то превратил в JSON Node.js.

Я пришел к Игорю с этими цифрами и ожидал услышать от него: "Ты, конечно, молодец. Node.js тоже молодец. В Node.js мы писать не будем. Давай все-таки трезво оценивать мысли". Но услышал абсолютно обратное. Если на Node.js можно, почему мы не можем его использовать? В этот момент я понял, что мы решим эту задачу.

Что мы сделали? Давайте вернемся к тем данным, которые у нас есть в HTTP-сервере.

На самом деле, у нас в HTTP-сервере все данные уже есть. Поэтому мы сделали следующий трюк. Вместо того, чтобы конвертировать данные, в V8 "пробросили" функцию, которая по ключу будет забирать эти данные.

Мы выбросили сразу любые конвертации, которые у нас занимали 4 миллисекунды. Осталось время на проброс. Но оказалось, что это время - 1 миллисекунда. При ограничении в 4 миллисекунды… Напоминаю, что это список писем. Мы получили те миллисекунды, к которым стремились. Допустили к этому реальных пользователей. Не то чтобы они это видели, но ситуация стандартная.

Мы взяли один из серверов и выключили его из балансировки. Подняли там всю инфраструктуру, по которой отрисовывается список писем. "Поспритили" часть реальных запросов настоящих пользователей с настоящими данными и настоящим списком писем. Просто послали данные на этот сервер (продублировали). У нас тут результаты 30-часовых нагрузочных тестов.

Да, кстати, эти результаты показало одно ядро, не сервер, хочу отметить. Сервер обработал 10 миллионов хитов за 30 часов. Среднее время трансформации составило 1,6 миллисекунды. Не то чтобы сервер оказался быстрее. Просто у реальных пользователей разные настройки… Мы-то все тестировали на 25-ти письмах, а реальных пользователей просто местами меньше. Цифра ниже как раз показывает, что полпроцента, допустим, вообще-то укладывались в десять миллисекунд. Но, видимо, просто у них стоит в настройках 200 писем.

Думаю, что цифры прямо пояснять не надо. Тут видно, что у большинства как раз меньше двух миллисекунд. 90 % запросов меньше двух миллисекунд.

Все-таки надо добираться до реальных пользователей, настоящих, чтобы они это увидели своими глазами, хоть им и все равно.

Когда мы с Игорем с цифрами, которые мы получили, пришли к Ермакову (тогда он был заместителем технического директора), я сказал: "Нагрузочные тесты хорошие, цифры хорошие, задача реальная. Хотим запускать это на пользователей". Как в любой большой компании, у нас есть пара проектов, которые лежат. Вроде как и надо их делать, а вроде и не надо. Я говорю: "Давай мы сделаем какой-нибудь из таких проектов на V8 на fest. Если что-то пойдет не так, то ничего страшного. Забудем про этот проект точно так же, как и забывали последний год. Но если все будет хорого, мы получим работающую штуку, от которой можно отталкиваться".

Игорь посмотрел на меня, посмотрел на цифры и говорит: "Цифры хорошие. Но ты правда хочешь V8 на продакшне?" Я говорю: "Хочу". Он ответил: "Тогда начинай с главной страницы. Иначе эти "сопли" так и будут размазываться дальше". Это была одна страница. Верстка заняла три дня. Мы опять выделили один из серверов front-end и запустили туда половину обычной нагрузки. Получили потребление ресурсов процессора в три раза больше, чем соседний front-end, который сейчас работает на пользователей.

Выглядело это вообще как полнейший провал. На самом деле, мы очень крупно ошиблись (я потом покажу, где). Но это нам дало еще оптимизации. Все было очень грустно, потому что мы настолько были погружены в эту задачу, что у меня весь код шаблонизатора был в голове. Я мог его перебирать в мыслях, не обращаясь к IDE. Я не понимал, где мы могли так проиграть по времени, потому что во всех предыдущих тестах все было хорошо.

Мы проиграли в шесть раз по сравнению с текущим сервером.

Не первый раз, что называется. Стали смотреть. Главная страница занимает 165 килобайт. Из них V8 генерирует 65 килобайт. Остается 100 килобайт. У нас есть технология RB внутри. Чтобы вы понимали, главная страница - это все-таки витрина остальных проектов. Поэтому RB - это средство доставки данных от этих проектов на главную страницу. RB в HTTP-сервер отдает уже, в общем-то, в виде html. Его шаблонизировать не надо. Если надо, то этим RB занимается внутри себя. Поэтому происходит следующее.

У нас есть технология RB. Она "общается" с HTTP-сервером, HTTP-сервер отдает результат V8. V8 конкатенирует это со своими данными, отдает обратно. Еще одно примечание для тех, кто ничего не читал про V8. Он внутри у себя все держит в utf-16. Это означает, что мы… А у нас шла, естественно, от utf-8. Utf-8 utf-16 обратно в utf8.

Вот как выглядел изначально скомпилированный код в очень упрощенном виде.

У нас есть некоторая строка. С этой строкой конкатенируется результат работы RB, и дальше он конкатенируется со следующей строкой V8.

Возникает вопрос: если с RB мы ничего не делаем, зачем ее вообще отдавать V8 и тратить на это ресурсы? Поэтому мы сделали небольшой хак. Из "Си" пробросили еще две функции. Это функция push.К счастью, главная страница у нас сплошная. Поэтому там не надо мучиться с "set" и "get".

Не заостряйте на этом внимание. Если кто-то будет досконально разбираться, вы поймете, о чем я говорю. Там шаблонизация сплошная, вернее, линейная. Как только шаблон отработал "кусок", мы точно знаем, что его можно отдавать пользователям.

У нас следующее происходило: библиотека V8 генерировала по своей логике часть строки и отдавала ее сразу в HTTP-сервер. Он его может сразу отдавать клиенту. Дальше, по логике, нужно выдать кусок из RB. Мы этот кусок даже не получаем. Мы HTTP-серверу сразу говорим: "Бери этот кусок и отдавай сразу клиенту - нам от него ничего не нужно". Получили еще 30 %. Но, на самом деле, оставался проигрыш в четыре раза.

Я заподозрил что-то неладное. Взял вот эту нагрузку, количество хитов, которое мы отправляем на тестовый сервер. Умножил на количество серверов, которое у нас сейчас "поднято", и получил вот это количество хитов в сутки.

440 000 000

А по всем TNS-счетчикам у нас вот столько хитов в сутки.

110 000 000

Стали смотреть логи и обнаружили забавную вещь: на каждый полезный запрос у нас приходится четыре запроса что-нибудь скинуть в логи. Мы, радостные, влезли ровно в те ресурсы, которые нам нужны. Это демонстрирует вот эта картинка.

Уже на работающем сервере мы некоторое время имели возможность переключаться между шаблонизаторами. Верхний график - это память. V8 памяти нужно, конечно, чуть-чуть больше. Провал по памяти - это как раз переключение на старый шаблонизатор. Нижний график - это как раз "камень". По нему изменений нет. Мы запустили это все на пользователей.

Сейчас вся главная страница Mail.ru отдается через V8.

V8 на данный момент генерирует 65 килобайт данных. Может быть, сегодня уже чуть-чуть больше - я все-таки две недели готовил доклад. Время, которое библиотеке нужно для генерации этих данных - одна миллисекунда. Плюс 40 мегабайт на контекст с учетом того, что у нас количество ядер на сервере доходит до 8, не знаю, или до 16-ти. Это, в принципе, не очень напрягает, проблем с этим нет.

Это почти история успеха. Здесь присутствует Игорь Сысоев. Все, кто когда-нибудь думал про V8 на сервере, конечно, должны были прочитать его статью о проблемах V8.

Если я не прав, надеюсь, он меня поправит. Насколько я помню, там речь идет вот о чем. В V8, во-первых, контекст поднимается не мгновенно. Если говорить про реальные проекты, то у нас контекст поднимается с поднятием HTTP-сервера и только один раз. Это занимает около 2 миллисекунд, и, в общем, с этим можно жить.

Вторая проблема. Если вдруг случилось так, что библиотека V8 хотела выделить себе память и не смогла, она в этот момент "падает". В момент, когда мы занимались этим шаблонизатором c JavaScript на сервере, мне удалось выйти на разработчика V8 - Вячеслава Егорова. Он, кстати, часто выступает, это известная личность. Можно легко найти информацию про него. Он эти догадки почти подтвердил. Он утверждает (мы не проверяли, честно), что в текущей реализации V8, если ему не удалось аллоцировать память, выбрасывает исключение (англ. exeption), которое можно перехватить.

Но он честно признал, что это исключение может помочь только вовремя перезапуститься, притом вообще целиком. Контекст умер, и с ним ничего сделать нельзя. Он ни на что не откликается и все также остается в памяти.

Что касается Nginx, скорее всего, такое поведение критично. У нас в Mail.ru, по заверениям одного из коллег, в том же самом RB работа в условиях нехватки памяти считается штатной ситуацией. А там, где нехватка памяти считается штатной ситуацией, скорее всего, будут проблемы. Но если мы говорим про обычный проект, то если у вас вдруг начнет заканчиваться память на машине, вы будете думать не про V8, а совсем про другие вещи.

Еще один неприятный момент.

Это статья про V8. Trunk V8, оказывается, очень активно разрабатывается. Самому Вячеславу не удалось воспроизвести эту ситуацию. Но у нас она воспроизводится "на ура". Я надеюсь, что мы поможем ему ее решить.

В какой-то момент мы запустили V8, и у нас обнаружалась утечка памяти. Мы очень долго искали утечку у себя, а потом переключились с trunk на версию 3.6.8, и проблема исчезла. Trunk проблемный. Стабильная версия Node.js - это, на данный момент, 06.14, и она тоже живет на 3.6. Если вы работаете с trunk, помните: вы работаете с чем-то, что не очень стабильно себя ведет. Все-таки переключайтесь на стабильные версии, за которые разработчики отвечают.

Как раз ссылка на API V8, которая очень понадобится, если вдруг этим займетесь. Ссылка на наш шаблонизатор. Я не могу сказать, что он OpenSource по одной простой причине. На самом деле, OpenSource предполагает некоторую ответственность за то, что ты делаешь. Мы ее, скорее всего, ее на себя возьмем, но конкретно сейчас мы к этому не готовы. Поэтому я просто говорю, что мы просто в открытую разрабатываемся.

Все, что я говорил про V8 trunk, может случиться и у нас. На данный момент мы ничего не стабилизируем. Это продукт, который мы разрабатываем для себя, но в открытом виде. С оглядкой на то, что мы его хотим выложить в OpenSource. Если будут какие-то просьбы типа "pull request" (они уже были, кстати, от некоторых людей), то такую заинтересованность мы, конечно, приветствуем.

У меня все. Я готов ответить на ваши вопросы.

Вопросы и ответы

Реплика из зала : Спасибо за доклад. У меня такой вопрос. Я не очень понял предысторию, почему нужно было использовать V8. У вас был сайт…

Андрей Сумин : Чуть больше, чем один сайт.

Реплика из зала : У вас были скрипты. Эти скрипты на серверах back-end стали тормозить. Так было или не так?

Андрей Сумин : Нет, совсем. Под "сайтом" вы понимаете проекты "Mail.ru Почта"?

Реплика из зала : Да.

Андрей Сумин : Основных проблем несколько. Первое - у нас двойная шаблонизация. Шаблоны, которые применяются на сервере, нельзя использовать на клиенте. Это проблема. Список писем мы обязаны отрендерить и на сервере, и на клиенте. Поэтому нам нужны были шаблоны, которые работают везде. Нужно, чтобы один и тот же шаблон выдавал один и тот же результат и на клиенте, и на сервере. Это первая и самая главная предпосылка для перехода на V8.

Вторая предпосылка. У меня в команде много людей, которые знают JavaScript. Но нет ни одного человека, который в достаточной для Mail.ru квалификации знает, допустим, "Cи", Python, или что-либо подобное. У меня есть люди, которые способны писать быстрый, хороший JavaScript, без утечек памяти и так далее, и тому подобное. Но нет людей, которые способны это писать на другом языке. Не потому, что они, в принципе, не способны, а потому, что они этим не занимаются каждый день. Плюс к тому, что нужны единые шаблоны на клиенте и на сервере, еще нужны специалисты, которые могут это сделать. Это две основные причины.

Реплика из зала : Нет, вот, смотрите. Если мне нужно список отрендерить на сервере, я там рендерю этот список, и каждому элементу там присваиваю какой-нибудь идентификатор. Или группе элементов присваиваю класс. Пишу на JavaScript скрипт, который что-то там делает. Может, замены какие-то, не знаю… Просто не совсем понятна мотивация использования. Почему двойная шаблонизация получается?

Андрей Сумин : У нас на "Почте" есть список писем. Он составляется из каких-то данных, его надо отрисовать. Дальше мы приходим на клиент, и список писем у нас обновляется раз в какое-то время (потому что человеку могут прийти новые письма). Соответственно, его надо повторно рендерить. Он приходит, естественно, на клиент в виде данных. "Гонять" html затратно по времени. Он приходит в виде какого-то JSON.

Плюс у нас есть разного рода оптимизации. Пришел JSON, его надо превратить в html. Это, опять же, шаблонизация. Плюс списки писем. Для каждого письма достаточно информации, чтобы при клике на письмо пользователю уже можно было что-то отобразить - допустим, заголовок письма. У нас есть тема, автор, мы можем это отобразить, дожидаясь контента письма. Это еще одна шаблонизация.

На клиенте у нас очень много шаблонизаций. Всякие ухищрения типа "я добавлю идентификатор и напишу простенький код на JavaScript" реально не работают. А когда человек находится на странице с письмом, он там может нажать F5, и в этот момент мы должны отрендерить это письмо на сервере, потому что так быстрее, как ни странно.

Письмо - это такой непростой объект. Там очень много всякой логики шаблонизации. "От кого", "Кому", "Прочтенное", "Непрочтенное", "Важное", "Не важное". Есть вложения, нет вложений. Много всего.

Реплика из зала : Ясно. Спасибо.

Реплика из зала : У меня два вопроса. Первый: почему вы выбрали V8. Смотрели ли вы в сторону JavaScript от Mozilla (SpiderMonkey, TraceMonkey)?

Андрей Сумин : Честно говоря, мы в его сторону посмотрели. Лично я выбрал V8, скорее, по политическим соображениям. Большая компания делает продукт для себя. Причина была такая. Когда мы стали результирующие шаблоны запускать на клиенте, в частности, шаблон со списком писем в Chrome "экспандился" за 6 миллисекунд, а в Mozilla - за 3 миллисекунды. Я задумался: может быть, неправильно был сделан выбор?

Мы в результате все-таки достигли 1 миллисекунды (для сравнения: у нас сервер в Gzip тратит сильно больше времени, чем V8). Я решил пока остановиться на V8. Может быть, V8 еще обгонит SpiderMonkey. Хотя на данный момент на нашем шаблонизаторе SpiderMonkey быстрее.

Причины пока политические. С учетом того, что по скорости нам некуда дальше гнаться. Другие проблемы сейчас надо решать.

Реплика из зала : Второй вопрос. Как сервер выглядит с точки зрения архитектуры? Это один процесс, который обрабатывает кучу соединений, или как?

Андрей Сумин : Игорь ответит - он писал это. Лучше ему право ответа отдать.

Игорь Сысоев : Да, это один процесс, который обрабатывает кучу соединений.

Реплика из зала : Понятно. Epoll Linux у вас?

Игорь Сысоев : Да. Один процесс.

Реплика из зала : Все соединения обрабатываются в одном контексте?

Игорь Сысоев : Да, один контекст.

Реплика из зала : Понятно. Спасибо.

Реплика из зала : У меня вопрос, во-первых, про переопределение шаблонов. Как решить такую задачу? Скажем, у нас есть какой-нибудь блок, мы хотим на какой-нибудь странице вывести этот блок в точности и добавить к нему еще что-то. Добавить какую-то дополнительную информацию. Переопределиться с вызовом базового блока. Ты сказал, что у вас есть JavaScript. Как это будет выражаться? Придется написать ассемблерную вставку в этот XML-синтаксис на JavaScript?

Андрей Сумин : Вариантов несколько. Естественно, это может быть ассемблерная вставка, либо можно самому расширить язык шаблонов. Если нам это понадобится, и мы не проиграем по скорости, мы это сделаем, может быть, в текущем шаблонизаторе. Если это нужно вам, и вам требуются такие же скорости, то можно просто расширить текущий синтаксис своим. Либо прислать нам патч.

Реплика из зала : Нет, я-то, конечно, понимаю, что такие семантические удобства "бесплатно" относительно скорости не даются. У меня вопрос в другом. Какие у вас тогда библиотеки блоков, что вам не нужно переопределяться с вызовом базового метода?

Андрей Сумин : Сейчас я не очень понял вопрос.

Реплика из зала : У вас есть какие-то библиотеки, которые потом используются и в которых есть переопределение? Или просто все написано на одном проекте в один слой, и никаких переопределений нет, потому и никакой богатой семантики не нужно.

Андрей Сумин : Один проект точно написан так. Сейчас пишутся еще два или три проекта. Там мы уже задумываемся об этих вещах. Эти два-три проекта немного попроще относятся к производительности. Скорее всего, там мы будем это дорабатывать.

Реплика из зала : У меня еще один вопрос. Ты рассказывал, как здорово, что есть классные JavaScript-специалисты. Для них все это понятно и удобно. Зачем тогда XML-синтаксис, почему бы и синтаксис не уложить в JavaScript-синтаксис? Тогда специалист будет использовать "родной" JavaScript-редактор, например. Банально код ему будет проще читать, поскольку это один и тот же код. Мы вроде бы унифицировали исполнение этих шаблонов с точки зрения движка. Но с точки зрения синтаксиса мы, наоборот, создали вторую сущность.

Андрей Сумин : Сущность создали. Но тут несколько причин. Это следствие нескольких решений. Во-первых, я упоминал, что задача, которую нам точно надо было решить, - это научиться "готовить" JavaScript на сервере. Для того, чтобы ее решить, не хотелось отвлекаться на придумывание синтаксисов, решение проблем с "эскапированием" и так далее.

Реплика из зала : Но в JavaScript придуман синтаксис, и решены проблемы с "эскапированием". Точно так же, как с XML.

Андрей Сумин : Нет, когда ты шаблонизируешь на JavaScript, то в любом случае придумаешь свой язык.

Реплика из зала : Почему? Почему я придумываю язык, а не пишу на JavaScript?

Андрей Сумин : Потому что конкатенировать строчки на JavaScript - это очень неблагодарное занятие.

Реплика из зала : Ладно. Не понимаю.

Андрей Сумин : Второе. Может быть, когда JavaScript на сервере станет обычным делом на многих проектах (может быть, не для всего проекта), по крайней мере, когда к этому будут относиться как к обычной практике, скорее всего, появится другой шаблонизатор, не fest. Очень может быть. Почему нет?

Но это уже задача другого порядка. Сейчас ее решать не нужно было, потому что были проблемы поважнее.

Второе. 99 % наших выходных документов - это XML. Когда у тебя управляющая конструкция XML и на выходе XML, проекту сильно проще, потому что XML понимают редакторы. Они же это валидируют, плюс есть куча инструментов по работе с XML. На текущий момент это финальное решение. Что будет через год, я не могу сказать.

Реплика из зала : Окей, спасибо. Я заканчиваю. Да, правильно ты говоришь, на мой взгляд. Самая главная проблема - это привнести JavaScript на сервер, а дальше это можно уже оптимизировать. Это, грубо говоря, принципиальный шаг. А дальше идут уже непринципиальные мелкие улучшения, которые можно сделать в будущем. Спасибо за то, что ты тоже такой опыт провел.

Реплика из зала : У меня вопрос в продолжение одного из предыдущих - про один процесс. Это действительно один процесс, к которому идут все запросы? Или это несколько процессов на разных портах, куда через upstream, допустим, Nginx передает различные запросы от разных пользователей?

Игорь Сысоев : Нет. Там нет Nginx. Там - да, один процесс, куда все идет. На самом деле, там на одной машине несколько форков. Все они на ядрах "висят". Туда как-то балансируются клиенты.

Реплика из зала : Понятно. Спасибо.

Реплика из зала : Еще такой вопрос. Несколько лет назад я тоже пытался заниматься JavaScript на сервере. С отладкой был вообще сущий ад. Какие инструменты отладки используете, чтобы все это профилировать?

Андрей Сумин : У нас сейчас есть очень большой бонус - это Node.js. Все, что мы запускаем, "из коробки" работает в Node.js. Там достаточно хороший инструмент отладки. Плюс, если мы говорим именно про шаблонизатор, мы всегда можем свой шаблон отправить на клиент. Там еще больше инструментов отладки.

Третье: как я уже говорил, могут быть какие-то специфические накладки со средой. Можно сделать так, чтобы среда просто "пробрасывала" функцию. У нас функция лог "пробрасывает". На самом деле она "log", "var" и "error" "пробрасывает", и V8 может "общаться" с внешним миром. Там уже среда куда-то выведет эти данные. Плюс еще в самом V8 API есть очень много средств работы с V8 через API V8. По крайней мере, когда он "падает" - он через API говорит, что он "падает".

Реплика из зала : Спасибо большое.

Реплика из зала : Спасибо за внимание.

Андрей Сумин : Спасибо всем.