Прототип тоталитарного фреймворка для node.js

    на переработкуЕсли Вы программируете на node.js, но устали писать роутинг запросов в коде, у Вас нет предубеждения против использования глобальных переменных в служебных целях и Вы согласны, что излишняя свобода губительна для масс, то тоталитарный кружок выходного дня приготовил для Вас прототип альтернативной платформы для разработки веб приложений. Предупреждаю, что тоталитарный стиль предполагает не встраивание фреймворка в приложение через require, а наоборот, встраивание своего приложения в структуру фреймворка, где фрагменты кода приложения будут на каждом шагу иметь дело с дополнительными ограничениями и навязанными структурами кода и данных. Про то, что «less-than-expert» смогут разрабатывать высокопроизводительные системы, как утверждают разработчики ноды — ну тут Вы сами понимаете, что это будут за системы, особливо асинхронные, с потерянными коллбеками и утечками памяти на каждом шагу. В плане защиты от дурака, сей прототип чудес не доставляет. И конечно же, ожидаю от Вас много конструктивной критики, потому, что прототип сырой, хоть и собрал в себе множество концептуальных наработок нашей команды за последнее десятилетие. Даже название Impress появилось всего два дня назад и, да — это самый сложный вопрос.

    Возможности и особенности


    • Маршрутизация URL на базе файловой системы — создал каталог — вот уже и часть URL, каталог незамедлительно наследует все обработчики от родительского каталога, а все, что будет помещено в сам каталог, переопределит логику исполнения или внешний вид страниц. Т.к. в основном мы ориентируемся на одностраничные приложения с динамическим взаимодействием с сервером, то все это, по большей части, нужно не для генерации страниц, а для быстрого написания серверного API на базе JSON. Последовательность обработки запросов, сначала ищет и исполняет файл request.js в запрошенном каталоге или по цепочке родительских каталогов пока не найдет или не упрется в корень приложения, потом ищет и исполняет файл с именем, соответствующим имени HTTP метоада (get.js, post.js и т.д.), потом рендерит шаблоны.
    • Кеширование серверного JavaScript и шаблонов в оперативной памяти — все обработчики и шаблоны при первом обращении забираются в память, где снабжаются индексами для быстрого поиска при повторном использовании.
    • Возможность изменения кода приложений без перезагрузки основного приложения — за всеми каталогами, из которых считывались файлы, устанавливается слежение, и при изменении кода и шаблонов на диске, мы обновляем кеш, а при удалении файлов — очищаем кеш.
    • Обслуживание сразу множества портов, сетевых интерфейсов, хостов и протоколов — т.к. у нас statefull приложения, т.е. мы храним состояние в оперативной памяти (сессии пользователей и состояние других объектов предметной области), то очень полезно, чтобы все серверные компоненты, обслуживающие одного пользователя на разных портах (например HTTP и SSE), имели общую разделяемую память.
    • Несколько стратегий запуска:
      • Один процесс (все обслуживается в одном потоке обработки).
      • Специализированные процессы (для каждого порта свой worker + главный процесс master).
      • Многопоточная не специализированная обработка (однотипные workerы, не специализированные процессы, master принимает запросы со всех портов и распределяет их рандомно по workerам).
      • Многопоточная обработка с привязкой клиентов по IP адресам к определенным workerам (sticky by IP) для того, чтобы последующие запросы клиента потом попадали именно в тот worker, в котором уже развернута его сессия и другие структуры состояния, с ним связанные.
      • Специализация процессов с изоляцией по приложениям (не включена в релиз, тестируется) — если запущено несколько приложений в одной системе, и одно может мешать другому, то лучше разделить их в разные workerы.
      • Отпочковывание отдельных процессов для обработки определенных URL (не включена в релиз, разрабатывается) — это специально для обработчиков, которые предполагают долгую блокирующую логику, для этих случаев, схема с общим реактором не подходит, а мы отдаем пользователю «запрос обрабатывается», отпочковываем обработчик, а в конце обработки он вернет результаты по IPC в родительский worker, который уже, например, по SSE вернет их клиенту.
    • Встроенный простой шаблонизатор с поддержкой include, наследованием и переопределением фрагментов шаблонов в подкаталогах. Шаблонизатор имеет доступ только к данным, сформированным обработчиками бизнес-логики и помещенными в res.context. Если Вы спросите, почему я не прикрутил сюда готовый, например, EJS, то посмотрите их код, в EJS, например, куча синхронных операций, у других — другие дефекты, а ничего сложного то нам и не нужно для AJAX-приложений. На клиенте берите любой шаблонизатор — не регламентируется.
    • Поддержка cookies и механизм сессий — в том числе состояния в памяти или хранимого состояния в MongoDB, если worker падает, то при следующем запросе состояние подымется из базы, а сохраняется оно только при внесении изменений. Неопознанные ключи сессий отвергаются и удаляются.
    • Отдача статического контента и стриминг — тут еще много нужно дорабатывать, скоро появится кеширование статики в памяти и управление этим кешем в конфиге.
    • Встроенный маршрутизатор запросов (reverse-proxy) с поддержкой url-rewriting — можем отдельные URLы, по регулярному выражению, перенаправлять на другой хост и порт, обеспечивая возврат ответа пользователю от него.
    • Гибкая конфигурация в формате JSON с возможностью изменять без полной перезагрузки основного приложения (которая сейчас все еще иногда рушится).
    • Простое логирование запросов — его или нужно развить или прикрутить готовое решение.
    • Встроенные средства доступа к БД — в качестве БД пока только Mongo, но самостоятельно доступаться к другим базам не возбраняется.

    Планы на ближайшее время


    • Добавить поддержку оффлайн приложений (HTML5 Offline Applications Cache) с манифестами и т.д.
    • Реализовать загрузку файлов через POST запросы.
    • Кешировать статику в оперативной памяти с гибким управлением этим кешем из конфигурации.
    • Прикрутить опциональный geoIP лукап.
    • Сделать настраиваемые шаблоны страниц ошибок (404 и т.д.).
    • Ввести ограничения доступа к каталогам через файлики access.js, в них помещаемые.
    • Поддержка опционального вывода результатов API обработчиков не только в JSON, но и в XML (при моей индивидуальной непереносимости XML, прошу учесть сей героический шаг), а также, прием запросов в API в формате XML (держите меня семеро).
    • Простенькая CMS с хранением контента в MongoDB.
    • Плагин для отправки email из приложений.
    • Поддержка SSE (Server-Sent Events) для трансляции событий с сервера на клиенты по инициативе сервера (сейчас в разработке).
    • Демонизация и «graceful shutdown» (сейчас в разработке).

    Установка и настройка


    1. Ставим из npm (https://npmjs.org/package/impress)
    $ npm install impress
    

    2. Копируем шаблон проекта — содержимое каталога /node_modules/impress/examples/copyContentToProjectFolder переносим в каталог проекта (server.js, setup.js, config.js и каталог sites).

    3. Насраиваем файл config.js пройдемся по разделам конфига:
    databases — базы, которые будут автоматически открыты при старте и перечисленные коллекции будут доступны помещены таким образом: db.dbName.collectionName.find(...). Пример конфигурации:
    dbName: {
    	url: "mongodb://localhost:27017/dbName",
    	collections: ["collname1", "collname2"]
    }
    

    session — имя длина и набор символов для генерации сессионного cookie, имя базы для постоянного хранения сессий.
    cluster — настройка стратегии инстанциирования (тип многопоточнисти).
    servers — именованные сервера (интерфейс/порт), для каждого поле hosts — массив именованных хостов, которые нужно отдавать с этого сервера, static — массив масок файлов для отдачи статики, например ["/css/*", "/images/*", "/js/*", "/favicon.ico", "/index.html"].
    hosts — именованные хосты (виртуальные хосты), можно использовать маски для именования, например "*.myhost.com".
    routes — именованные маршруты переадресации запросов.

    4. Для инициализации структур данных в MongoDB, запустите node setup.js и жмем «y».

    5. В файле server.js можем написать еще дополнительный свой код на инициализацию:
    require('impress');
    impress.init(function() {
    	// Сюда свой код
    });
    

    6. И стартуем сервер
    $ node server.js

    Смотрим примеры


    1. Шаблоны: при запущенном сервере открываем http://localhost
    и шаблон смотрим тут: /sites/localhost/html.template
    Смотрим на приложение, нажимаем «Create account», «Sign In»

    2. Пример переопределения шаблона «left.template» с наследованием логики, открываем http://localhost/override
    и шаблон смотрим тут: /sites/localhost/override/left.template
    базовый шаблон тут: /sites/localhost/html.template
    обработчик серверной логики тут: /sites/localhost/request.js
    3. Пример API метода с JSON ответом смотрим: http://localhost/api/examples/methodName.json
    и код тут /sites/localhost/api/examples/methodName.json/get.js
    4. Пример запуска анонимной сессии: http://localhost/api/auth/anonymousSession.json
    и код соответственно: /sites/localhost/api/auth/anonymousSession.json/get.js
    5. Пример POST запроса: усилием мысли делаем POST на localhost/api/auth/regvalidation.json с параметром «Email»
    и код: /sites/localhost/api/auth/regvalidation.json/post.js
    6. Пример доступа в базу MongoDB:: http://localhost/api/examples/getUsers.json
    и код тут: /sites/localhost/api/examples/getUsers.json/get.js
    или вот он прямо:
    module.exports = function(req, res, callback) {
    	res.context.data = [];
    	db.impress.users.find({}).toArray(function(err, nodes) {
    		res.context.data = nodes;
    		callback();
    	});
    }
    

    Ссылки


    На Github: https://github.com/tshemsedinov/impress
    В npm: https://npmjs.org/package/impress

    UPD: Очень грубые тестирования на хостинге Hetzner EX4 (Intel Core i7-2600 Quad-Core, 16 GB DDR3 RAM) дали 22`572 запроса в секунду с шаблоном из 5 файлов и простеньким запросом в базу.
    Share post

    Comments 21

      +1
      Ещё пойти и в гуглогруппе советую объявить об этой новинке — английскою речью, понятное дело.
        0
        Ну я о грядущем релизе напишу, а то пока стесняюсь.
          0
          Опубликовал groups.google.com/d/msg/nodejs/mSbwcsiNGng/Df_TldIWriwJ
          Посмотрим, как буржуи будут есть тоталитаризм. В версии 0.0.16 уже много нового, но русскоязычная аудитория как-то не особо заинтересовалась, по существу только пару багрепортов.
          –1
          Подход к делу получился занятный.

          Тем не менее, вижу, что всё ещё не стало так просто, как оно было в PHP (где достаточно дать файлу некоторое расширение и обрамлять код специальным образом, а внешние по отношению к коду куски воспринималися просто как HTML).

          А было бы неплохо видеть «<?js var fs = require('fs'); var os = require('os'); ?>» и так далее; вот только не знаю, доживу ли до того дня.

          Но шаг, по крайней мере, в правильном направлении.
            +2
            Ну это весьма сомнительный синтаксис, если так сделать, говнокод начнется еще тот, бизнес-логика в шаблонах и поехали. Я не против логики в шаблонах в принципе, просто есть логика отображения, а есть бизнес-логика, и сие есть различные вещи. Идея — взять лучшее отовсюду, ну и это все же экспериментальный код, где можно позволить себе значительно больше.
              +2
              Я не против логики в шаблонах в принципе, просто есть логика отображения, а есть бизнес-логика, и сие есть различные вещи.
              Я знаю об этом различии, но не видывал ещё системы, которая могла бы автоматически разделить их. Только воля программиста, ограничивающая его и останавливающая от смешения того и этого, способна невозбранно достичь желаемого.

              А значит, и PHP-подобная система, в сущности, ничем не хуже любой другой в этом отношении. А в другом отношении (удобство разделения кода и представления внутри одного и того же файла) даже лучше, пожалуй. В своё время и Корпорация Microsoft, измыслив Active Server Pages (ASP), не далеко отошла от PHP-подобного синтаксиса.
                0
                Похоже, логическое разделение кода — это как раз то, чем человек отличен от животного и машины. Если не считать сказок об искусственной интелигенции, то человек — единственная тварь, способная строить абстракции и обреченная все время их рефакторить.

                А чем серьезно отличается маршрутизация URL на базе файловой системы от того, размещения php-скриптов по папкам, так это то, что есть одна точка входа, которая предварительно готовит все окружение, как то доступ к базе, предварительную обработку запросов и подгрузку библиотек. В результате, в каждом обработчике мы уже избавлены от необходимости повторять одинаковые предварительные ласки, и приступаем непосредственно к прикладной логике.
            +1
            Весьма амбициозный прототип. А почему в качестве основной поддерживаемой СУБД была выбрана именно MongoDB, а не РСУБД? Будет ли в планах модель для основной функциональности (юзеры, сессии, права доступа)? Планируется ли развитие в сторону полноценной CMS с модульной структурой, или проект не будет выходить за грани фреймворка?
              0
              Монго исключительно в качестве примера, доступ к данным — это не основная цель, но другие СУБД будут добавляться. Конечно, тоталитарный стиль не позволит добавить по несколько конкурентных СУБД одного типа, чтобы избавить разработчиков от мук выбора в том случае, если они сами не могут дописать что нужно. Как написано в разделе «Планы на ближайшее время» — простенькая CMS будет и очень скоро. Система юзеров и сессий уже частично выложена, а группы и права уже готовятся к публикации.
                +2
                Ну как же MEAN stack! Правда теперь уже MIAN.
                  0
                  «MEAN stack» — это (как нетрудно обнаружить вон там) сочетание MongoDB, ExpressJS, AngularJS и Node.

                  А чем в «теперь ужé MIAN» заменяют ExpressJS?
                    0
                    Ну так топик же про impress.
                      0
                      Скорее вышел AMIN (аминь) вместо AMEN (амэн)
                    +1
                    Вот не понимаю я этой тяги к повсеместному яваскрипту. В MongoDB нет транзакций, отсюда вытекает либо негибкая схема данных под атомарные файнд-апдейты, либо проблемы гонок при достаточных нагрузках, либо сущий ад с поддержкой двухфазных коммитов вручную. При этом, NodeJS прекрасно дружит с РСУБД, хорошо поддерживает разные коннекшен-пулы и хранимые операторы.
                      0
                      Может Вы хотите сказать, не к повсеместному яваскрипту, а к повсеместному JSON или повсеметсному бессхемному хранению? Кто ж виноват, если народ не видя разницы между RDBMS и NoSQL, не приходя в сознание, хранит все в том, что под рукой? Точно так же, многие используют РСУБД не по назначению, хранят там XML в текстовых полях и потом парсят все это, или хранят таблицы, на 95% заполненные NULL значениями в напрочь вырожденных структурах. Есть данные структурные и типизированные по своей природе, просто декомпозируемые и требующие вычислений «времени выборки», например: финансовые, учетные, транспортные, а есть данные бессхемные, по природе. Нет одной таблетки от всего.
                        +1
                        Я говорю именно о JS. Он используется в MongoDB при попытке сделать что-то сложнее выборки, например map-reduce. И много раз слышал девиз «везде JS, это клево».

                        По поводу проблемы выбора, вы правы. MongoDB — узкоспециализорованная субд для хранения и поиска данных документального типа. От этой ошибки и хочется предостеречь тех, кто еще не наступал на грабли. Простота и лаконичность MongoDB провоцирует выбрать её в качестве основной БД сайта. Мне вот тоже MongoDB вначале показалась идеальной, при её изучении и эксперементировании, до того самого момента, пока не появился сложный проект на ней, c «replica set»-ом на 4 серверах. Его нужно было поддерживать, а в нем начали появляться баги на нагрузках в состоянии «гонок», которые пришлось исправлять руками, симулируя транзакции хранением состояний элементов коллекций (маны по ним). Далее добавились «хотелки» от владельца, что привело к проблемам с модификацией схемы (малейшее поползновение может привести к переписыванию многих частей кода). Делая это, часто в голову приходила мысль «черт, был бы тут postgres — отделался бы одной строчкой».
                          +1
                          Ну конечно, любые вычисления внутри update/insert приводят в Монге к вытягиванию данных и обработке их при помощи JS. Это не говоря уже про то, что удаление или изменение связанных ссылками объектов (реализация аналога каскадных операций из RDBMS) становится сущим адом. Простая вещь, умножить поле на 2 по всей коллекции — уже туго. Я это хорошо понимаю, т.к. еще в 1999 году написал СУБД UOD, нечто подобное на Монго, и тогда еще не было ни какого JSON, поэтому у меня был свой формат представления объектов USP и свой двоичный формат для хранения типа BSON. Так вот все это хозяйство использовалось не вместо RDBMS, а вместе с ними, для разных данных разных типов. Конечно, хотелось бы иметь гибридную СУБД, не обертку над двумя и не имитацию одного принципа моделирования данных при помощи другого, а действительно гибридную, совмещающую: таблицы, связи, деревья, бессхемные коллекции, файлы. С реализацией целостности данных в таком хранилище, но на горизонте ничего такого не знаю.
                            +1
                            Есть же вполне гибридная ArangoDB с одной стороны и PostgreSQL c индексируемыми json колонками с другой.
                              0
                              А еще есть функциональные индексы в postgres. Инструмент не столь удобный и понятный, как индексы в mongo, но может спасти в некоторых случаях.
                                0
                                Так именно их я и имел ввиду. Очень даже понятный инструмент и его часто не хватает в других субд.
                  0
                  Оцените: версия 0.0.3 получила шикарное расширение для шаблонов, позволяющее избежать дополнительных IFов в коде, например: при включении в страницу шаблона с именем «hmenu» происходит такая последовательность поиска:
                  1. Если нет сессии, то отдается hmenu.template
                  2. Если есть сессия, и пользователь входит в группу admin, то ищется hmenu.admin.template, если нет такого файла, то берется hmenu.template
                  3. Если есть анонимная сессия или у пользователя нет группы, то ищется hmenu.everyone.template, если нет такого файла, то берется hmenu.template
                  4. Если ни один из вариантов выше не позволил найти файл, то смотрим на каталог выше пока не дойдем до корня сайта

                  Это позволяет без IFов кастомизировать страницу под разные группы пользователей.

                  Only users with full accounts can post comments. Log in, please.