Pull to refresh

Impress: многоцелевой сервер приложений для Node.js

Reading time 7 min
Views 17K
Несмотря на заметные успехи, Node.js все еще остается специализированной технологией, которой преимущественно закрывают узкие места в системах, написанных в другом стеке технологий. Причина такого положения кроется в том, что сама по себе нода не имеет многих библиотек, к которым мы привыкли на других языках и которые обеспечивают быструю разработку именно прикладного ПО. Например, для того, чтобы разделить в коде обработчики разных URL, отдавать статические файлы, организовывать сессии, запускать нескольких потоков, иметь доступа к БД, кешировать данные в памяти, разграничивать права пользователей, иметь логи и ротировать их, создавать сетевое API, рендерить шаблоны, настраивать URL-реврайтинг, обеспечивать быструю доставку событий с сервера на клиенты, для всего этого, и многих других задач, используются отдельные библиотеки (модули). Разные модули написаны разными разработчиками, сложно стыкуются, конфликтуют. В общем, мы решили, весь этот набор обязательного функционала, необходимого практически в каждом веб-приложении, объединить в один сервер приложений и повысить, таким образом, связанность кода, сделать ядро сервера приложений монолитным и более согласованным, чем решения, собранные из отдельных библиотек. Проект Impress уже анонсировался как прототип, а сейчас предоставляет весь необходимый арсенал для быстрой разработки приложений, что протестировано на десятке живых проектов. Impress значительно отличается от другой широко распространенной платформы так же, как импрессионизм отличается от экспрессионизма, то есть, производит целостное, хорошо продуманное эстетическое впечатление, в противоположность внезапному выбросу эмоций. Но мы, не вовлекаясь в критику чужого кода, перейдем к демонстрации конструктивных особенностей Impress.

Немного метрик кода


  • Размер ядра Impress (с максимальной связанностью кода) /lib/impress.js — 44Кб
  • Размер всего кода Impress (с высокой связанностью, но не обязательным подключением) /lib/* — 110Кб
  • Размер внешних библиотек (с необязательной загрузкой), все что в /node_modules — 56Мб
  • Из них модуль geoip-lite со своей базой данных — 40Мб
  • Способ загрузки библиотек ядра: экзотический (описан тут — Паттерны JavaScript модулей в Impress для node.js и браузеров).
  • Возраст проекта: 3½ месяца (до этого еще месяц в качестве прототипа, до публикации в npm и github).
  • Интенсивность развития: за это время вышло 47 версий, т.е. что-то доделывается каждые 2-3 дня.
  • Зависимости (на текущий момент): async, cluster, colors, mkdirp, mongodb, mysql, memcached, nodemailer, geoip-lite, uglify-js, multiparty, iconv-lite.

Возможности и сферы применения


  • Создание многостраничных веб-приложений, то есть, с серверным шаблонизатором (встроен в ядро) и с перегрузкой страниц.
  • Создание одностраничных веб-приложения с обменом данными между браузером и серверным API при помощи AJAX, с передачей фрагментов HTML или JSON, для динамической перестройки клиентского экрана из оных.
  • Создание гибридных решений, где смешаны одностраничный и многостраничный подходы.
  • Разработка сетевого API для межсерверного и клиент-серверного взаимодействия, в том числе, с браузерами, мобильными приложениями для iOS, Android и т.д.
  • Оффлайн приложения HTML5 с cache.manifest, локальным хранилищем в IndexedDB или WebSQL и возможностью работать как в онлайне, так и в оффлайне, в автономном режиме. Это не задача Impress, конечно, но есть опыт применения даже в таком нетипичном случае.
  • Обслуживание множества доменов одним сервером приложений (в том числе по маске), т.е. механизм настройки виртуалхостов составляющих одно приложение или запуск на них разных приложений (как на одном, так и на разных портах).
  • Проброс вызовов на другие серверы и порты (reverse-proxy) с поддержкой url-rewriting, настройка маршрутизации URL в config.js в формате JSON и при помощи шаблонов и регулярных выражений. Проброс можно совмещать с обработкой части вызовов в сервере приложений. При помощи проброса можно собрать одно приложение из нескольких языков, серверов и технологических стеков.
  • Отдача статики с кешированием в оперативной памяти, минификацией статических браузерных js-файлов, со сжатием gzip для сжимаемых форматов и правильной отдачей HTTP 304 (Not Modified) при получении заголовка «if-modified-since».
  • Есть встроенная система аутентификации и провайдер для хранения пользователей в MongoDB.
  • Есть встроенная система сессий с хранением их в оперативной памяти, сохранением в MongoDB, восстановлением сессий при оперезагрузке. Есть возможность делать не аутентифицированные сессии, т.е. идентифицировать и хранить состояние пользователя без регистрации.
  • Возможность работать в несколько процессов с поддержкой нескольких стратегий распределения задач по процессам и с реализацией взаимодействия между ними через IPC (в дальнейшем предполагается использование ZeroMQ для этих целей и прозрачное масштабирование на несколько серверов). Обмен сообщениями применяется если нужно наладить взаимодействие между клиентами, подключенными к разным процессам сервера.
  • Реализация двух способов приклеивания сессий к процессам: «IP sticky» (приклеивание по IP) или «cookie sticky» (приклеивание по кукизу, применяется в паре с внешним балансировщиком и мультиплексированием по портам). Это позволяет все соединения с одного IP или c одним и тем же кукизом при повторных запросах (после аутентификации) опять направлять в тот же процесс, который хранит их сессию (состояние).
  • Логирование запросов с заведением нового файла каждые сутки и удалением старых файлов (устанавливается лимит хранимой истории).
  • Сейчас заканчивается работа над CMS, которая уже встроена в ядро Impress и скоро получит админ-интерфейс для редактирования страниц.

Ну и те возможности, о которых не буду подробно, т.к. уже писал о них и их лучше смотреть на примерах:
  • Маршрутизация URL на базе файловой системы (мапинг URL в каталоги).
  • Кеширование серверного JavaScript и шаблонов в оперативной памяти.
  • Возможность изменения кода приложений без перезагрузки основного приложения (процесс следит за изменением файлов на диске.
  • Несколько стратегий запуска: multiple, single, specializatio, sticky.
  • Возможность изменять конфиг без полной перезагрузки основного приложения (процесс следит за изменением файла config.js).
  • Поддержка SSE (Server-Sent Events) с системой трансляции событий с сервера (в стиле PUSH) через открытое соединение на клиентскую часть без постоянных запросов с клиента (в стиле PULL).

Надстройка над драйвером доступа к MySQL:

Так же разработан метаязык на базе синтаксиса JSON, который позволяет удобно и кратко описывать структуры реляционных БД и транслировать потом эти структуры в SQL скрипты. См. примеры в каталоге /node_modules/impress/schemas/ Для трансляции можно использовать такой код:
var schemaCore = require('./schemas/impress.core.schema.js');
var scriptCore = db.schema.mysql.generateScript(schemaCore, true).script;
console.log(scriptCore);

Далее, схемы можно использовать для скафолдинга форм, гридов и вообще пользовательских интерфейсов, но это тема для отдельной публикации.

Примеры


Лучше всего понимать на примерах, которых в Impress достаточно. Сразу после установки из NPM-репозитория (npm install impress) мы можем развернуть примеры, скопировав их из папки /node_modules/impress/examples/copyContentToProjectFolder в корень проекта. Для запуска желателен MongoDB для хранения сессий (в ближайшее время будут реализованы и другие провайдеры хранения сессий). Но можно запустить примеры и без БД. Если же MongoDB все же есть, то нужно в config.js установить «session.persist» в true, раскомментировать «databases.impress», в том же конфиге, и в секции «plugins.require» раскомментировать модули: «db», «db.mongodb», «impress.security.mongodb». После этого создать необходимые коллекции и индексы запустив: node setup.js и потом запустить сервер приложений: node server.js

API (RPC): STATEful и STATEless


Одна из основных задач, для чего разрабатывался Impress — это создание серверов приложений как на принципе STATEless (т.е. REST серверов), так и на более интересном принципе STATEful. Нужно напомнить, что REST, это когда между парой запрос/ответ ни на сервере, ни на клиенте, не сохраняется состояние объекта. В противоположность RPC, на котором основаны клиент-серверные приложения, в которых принято создавать модель в клиенте и создавать модель в сервере, связывая их интерфейсы по сети и транслируя между этими моделями события и вызовы. Вот нода позволяет развернуть модель на двух концах провода и синхронизировать через AJAX/JSON вызовы, что конечно более удобно для прикладных приложений. REST пришел в ноду из каменного века тяжеловесных веб серверов (как Apache и IIS), которые каждый раз запускали внешние (по отношению к ним) приложения, передавая им запросы HTTP протокола через CGI. Такое приложение порождает новый процесс, он должен провести инициализацию рабочей среды, т.е. установить соединения с базой, развернуть все свои данные, прочитать себе из файловой системы что-то (если нужно) и т.д. и все это лишь для того, чтобы через несколько мил миллисекунд завершить работу и освободить память, отключиться от базы. До веба я писал на языках, в которых принято STATEful API как RPC (COM, DCOM, Corba...), и для меня концепции REST всегда не хватало. И вот, наконец, после перехода в вероисповедание ноды, мне было счастье. Теперь опять можно разворачивать в памяти данные и они никуда не деваются от запроса к запросу, можно хранить увесистые сессии в оперативной памяти и не делать сериализацию/десериализацию оных при завершении и повторном запуске процессов. И мне было видение, что REST ушел в прошлое вместе с костылями типа viewstate и серверами состояний. Понял я, что STATEful API есть величайшее благо, дарованное Всевышним каждому живому существу, познавшему ноду.

Чтобы сделать новый обработчик API-урла, нужно всего-то:
1. Создаем папку /api/myAPI/getSomething.json/
2. Кладем туда файл post.js и в нем пишем:
module.exports = function(req, res, callback) {
    db.impress.collectionName.find({ fieldName: req.post.fieldValue }).toArray(function(err, nodes) {
        res.context.data = nodes;
        callback();
    });
}

3. В каталоге /api/myAPI делаем файл access.js и в нем пишем:
module.exports = {
    guests: false, logged: true, http: true, https: true
}

Все готово, для https просто в config.js настройки ставим и в access.js запрещаем http для этой папки. Более того, создавать обработчики можно безе перезапуска сервера, просто создаем еще папку и пишем там код в файле. При первом обращении код попадает в память, при изменении файла на винте код подгружается новый в память и там сидит и ждет вызова.

Экраны из демонстрационного приложения


После установки и разворачивания примеров, можно увидеть такие экраны. На первом — форма регистрации пользователей, она работает при подключенной MongoDB, как и вся функциональность, связанная с аккаунтами и хранимыми сессиями (Create account, Sign In, Sign out).


В левом столбце кнопки, которые запускают примеры, их лучше смотреть со включенным Firebug или другим браузерным инструментом разработчика.


Самый большой пример, это универсальная админпанель для MySQL и MongoDB, о которой я уже писал. Вот ее скриншот:


На Github: https://github.com/tshemsedinov/impress
В npm: https://npmjs.org/package/impress
Tags:
Hubs:
+29
Comments 15
Comments Comments 15

Articles