Impress Application Server простими словами

Це не перша вступна стаття про Impress на Хабре, але за останній рік я отримав багато питань і придбав деякий досвід у поясненні архітектури і філософії цього сервера додатків і, сподіваюся, став краще розуміти проблеми і завдання розробників, початківців його освоєння. Та й у самому сервері відбулося досить змін, щоб назріла актуальність абсолютно нової вступної статті.

Impress Application Server (IAS) — це сервер додатків для Node.js з альтернативної архітектурою і філософією, не схожий на мейнстрім розробки під нодой і покликаний спростити і автоматизувати широке коло повторюваних типових завдань, підняти рівень абстракції прикладного коду, задати рамки і структуру програм, оптимізувати продуктивність коду, так і продуктивність розробників. IAS покриває зараз тільки серверні завдання, але робить це комплексно, наприклад, можна об'єднати на одному порту API, веб-сокети, стрімінг, статику, Server-Sent Events, проксіювання та URL-реврайтінг, обслуговувати кілька доменів і кілька додатків, як на одному сервері, так і на групі серверів, що працюють у зв'язці, як одне ціле, як один сервер додатків.

Введення

Для початку, я хочу перерахувати ряд проблем у загальноприйнятому підході для node.js, які спонукали мене почати розробку сервера додатків:
  1. Прикладної код часто змішаний з системним. Справа в тому, що нода, і більшість похідних фреймворків, занадто низькорівневі і кожен додаток обов'язково містить частину системного коду, що не відноситься до завдань предметної області. Ось і трапляється, наприклад, що додавання HTTP заголовка стає методом класу Patient і знаходиться у одному файлі з завданням роутінга URLов до цього пацієнтові і з відправкою подій через веб-сокети. Це жахливо.
  2. Нода дає надмірну свободу в плані архітектури додатків, яку складно відразу переварити не тільки початківцю, але навіть бувалому фахівця. Крім концепції middleware, якої, погодьтеся, замало для повноцінної розробки, немає поширених архітектурних патернів. Поділ проекту на файли, поділ логіки на класи, застосування патернів, створення внутрішніх API в додатках, виділення шарів і навіть структура каталогів — все це залишено на розсуд розробника. В результаті структура та архітектура проектів дуже відрізняється у різних команд і фахівців, що ускладнює розуміння і стикування коду.
  3. У світі ноди існує фанатичне поклоніння REST і, як наслідок, відмова від стану зберігання в пам'яті сервера. Це при тому, що програми Node.js живуть в пам'яті довго (тобто не завантажуються при кожному запиті і не завершуються між запитами). Слабке використання пам'яті ігнорування можливості розгорнути там модель розв'язуваної задачі на довгий час (завдяки чому, можна було б скоротити I/O до самого мінімуму) — це злочин проти продуктивності.
  4. Велика кількість модулів у npm — це сміттєві модулі, а серед небагатьох хороших, буває не просто знайти підходящий (кількість скачувань та зірок не завжди адекватно відображає якість коду та швидкість усунення проблем). Ще складніше скласти цілісне додаток з набору хороших, нехай навіть дуже хороших модулів. Всі разом вони можуть вести себе нестабільно і непередбачувано. Модулі не досить экранированны один від одного, щоб виключити конфлікти інтеграції (наприклад, якийсь модуль може перевизначити res.end або відправити http заголовки, а інший не очікують такої поведінки).
Є ще багато дрібних проблем, а глибоке горе з виловом помилок в Node.js це тема для трьох томів, залитих слізьми, кров'ю і кава (чай). Як наслідок усього перерахованого — нода, все ще викликає побоювання і, в більшості випадків, використовується як додатковий інструмент у зв'язці з іншими серверними технологіями, виконуючи підсобні роботи, як то: скриптування складання клієнтських додатків, прототипування або забезпечення доставки повідомлень веб-сокетам. Дуже рідко можна зустріти великий проект, що має серверну частину виключно на ноді.

Постановка завдання

Крім негативної мотивації (перераховані проблеми), були ще позитивні спонукаючі фактори для розробки IAS (ідеї та завдання):
  1. Масштабування node.js додатків більше ніж на один сервер, кожен з яких має свій cluster (кластер процесів, пов'язаних межпроцессовым взаємодією IPC).
  2. Обслуговування безлічі додатків в рамках як одного процесу, так і кластера процесів або ферми серверів з кластером процесів на кожному.
  3. Автоматична заміна коду в пам'яті, якщо він змінився на диску, навіть без перезапуску програми, через спостереження за файловою системою. Як тільки файли, подгруженные додатком, змінюються, то IAS читає їх у пам'ять. У якийсь момент в пам'яті може бути кілька версій коду, старий вивантажується як тільки завершена обробка всіх запитів, що прийшли до зміни, а новий вже використовується для наступних запитів.
  4. Синхронізація структур даних в пам'яті між процесами. Звичайно не всіх структур пам'яті, а тільки розгорнутого в ній глобального фрагмента моделі предметної області. Підтримуються адитивні зміни і транзакци, тобто якщо якийсь параметр инкрементируется паралельно в різних процесах, то зміни ці зливаються, адже їх порядок не важливий.

Філософія Impress

  1. Максимальне використання пам'яті. Швидше асинхронного I/O це тільки коли взагалі немає I/O або воно скорочено до мінімуму і виконується у відкладеному режимі (lazy), а не під час запитів.
  2. Монолітна архітектура і висока зв'язаність коду, всі основні модулі інтегровані, узгоджені і оптимізовані для роботи разом. Завдяки цьому немає зайвих перевірок, а поведінка при вирішенні типових завдань — завжди передбачувано.
  3. Мультиплексор портів, хости, IP, протоколів, серверів, процесів, програм, обробників і методів. Таким чином, ви можете поєднати на одному порту статику, API, веб-сокети, SSE, стрімінг відео і великих файлів, обробляти кілька додатків на різних доменах або многодоменные сайти і т. д.
  4. Принцип прикладної віртуальної машини ізольованою від оточення за допомогою пісочниць (sandboxes). Для кожної програми є свій контекст (область видимості), в який завантажено свої бібліотеки і дані. У додатку архітектурно передбачені місця для всіляких обробників: ініціалізації та фіналізації, моделей даних і структури БД, конфігурації і установки (першого старту), оновлення, міграції і т. д.
  5. Поділ прикладного і системного коду. Взагалі, розділяти шари абстракцій (більш високого і більш низького рівня) у додатку, набагато важливіше, ніж розділяти логіку з моделлю і поданням, в рамках одного шару абстракцій (їх іноді навіть ефективніше змішати).
  6. Мапінг URL на файлову систему з успадкуванням та перевизначенні по дереву каталогів. Але з можливістю і програмно додавати обробники прямо в пам'ять і прописувати роутинг руками в добавок до автоматичного роутингу за структурою каталогів.
  7. Стислість коду (див. приклади нижче) досягається завдяки розвиненому вбудованому API, який бере на себе все необхідне в переважній більшості випадків, може розширюватися та переиспользоваться від проекту до проекту. Так само стислості сприяє особливий стиль роботи з зонами видимості та розділення коду на файли з логічними частинами зручного розміру.

Область застосування

IAS розрахований для створення декількох типів додатків:
  1. Односторінкові веб-додатки з API і динамічним зміною сторінок на клієнта без перезавантаження сервера.
  2. Багатосторінкові веб-додатки з деякою ступенем динаміки на сторінках через API (логіка розділена на клієнтську і серверну).
  3. Багатосторінкові програми з перезавантаженням сторінок при кожній події (вся логіка на сервері).
  4. Програми з двостороннім обміном даними або потоком подій з сервера, інтерактивні програми (зазвичай це надбудова над варіантами 1 і 2).
  5. Мережеве API для доступу до сервера для нативних мобільних і віконних додатків.
Підтримуються кілька способів створення API
  • RPC API — коли URL ідентифікує метод з набором параметрів, порядок виклику важливий і як на клієнті, так і на сервері зберігається стан між викликами;
  • REST API, коли URL ідентифікує ресурс, над ресурсом можна виконувати обмежену кількість операцій (наприклад HTTP verbs або CRUD) запити атомарны, немає різниці у порядку виклику, і немає стану між викликами;
  • Шина подій: одно — або двосторонній потік клієт-серверної взаємодії через WebSockets або SSE (Server-Sent Events) використовується для повідомлень або синхронізації станів об'єктів між клієнтом і сервером;
  • Або змішаний спосіб.

Обробники

Аналогом middleware для IAS є handler (обробник) — це асинхронна функція, яка має два параметри (client і callback) і знаходиться в окремому файлі (з якого і експортується). Коли викликається callback, то сервер додатків IAS дізнається, що обробка завершилася. Якщо функція не викликає callback довше таймауту, то IAS повертає HTTP статус 408 (Request timeout) і фіксує проблему в логах. Якщо при виклику обробника відбувається виключення, то IAS бере на себе відповідь клієнту, вилов помилки та відновлення роботи оптимальним способом, аж до видалення та повторного створення пісочниці з зіпсованими або утекшими структурами даних.

Приклад API обробника:
module.exports = function(client callback) {
dbAlias.equipment.find({ type: client.fields.type }).toArray(function(err, nodes) {
if (!err) callback(nodes);
else callback({ message: 'Equipment of given type not found' }, 404);
});
}

Кожен HTTP запит може спричинити виконання декількох обробників. Наприклад, якщо запитаний URL
http://domain.com/api/examle/method.json
, а IAS встановлений в
/impress
, то виконання почнеться з каталогу
/impress/appplications/domain.com/app/api/example/method.json/
і проходить наступні етапи:
  • перевіряються права доступу до файлу access.js з цього каталогу, сесії (якщо є) і акаунта користувача (якщо є прив'язаний до сесії),
  • виконується обробник request.js з цього каталогу (якщо знайдений), він виконується при виклику будь-якого HTTP методу (get, post...),
  • виконується один з обробників відповідних методом HTTP запиту, наприклад get.js, put.js, post.js і т. д. (якщо знайдений),
  • виконується обробник end.js (якщо знайдений), він буде викликаний при будь-якому HTTP методі,
  • станеться серіалізація даних відповіді або шаблонизация сторінки (якщо це передбачено типом повертається відповіді),
  • результат виконання запиту відправляється на клієнт,
  • вже після цього, коли клієнт отримав відповідь і ми не затримуємо його, виконуються обробник lazy.js (якщо знайдений), який може, наприклад, зробити відстрочені операції, змінити/перерахувати або зберегти дані в БД,
  • на будь-якому етапі виконання в прикладному коді може статися помилка, що викликає необроблене виняток, але нам не потрібно обертати код в try/catch або створювати domain, це вже зроблено в IAS, при помилці буде викликаний обробник error.js (якщо знайдений).
Якщо в запитуваній каталозі немає потрібного обробника, то IAS буде шукати його на один каталог вище, поки не дійде до
/app
. Якщо обробник є в папці, то він може програмно викликати обробник з каталогу вище (або найближчий вгору по дереву) через
client.inherited()
. Таким чином, можна використовувати дерево каталогів для формування спадкування і перевизначення обробників. Наприклад, ви можете формувати дані відповіді в оброблювачі
/api/example/request.js
, а видавати їх у трьох форматах:
/api/example/method.json
,
/api/example/method.html
(містить ще й шаблони для виведення в html),
/api/example/method.csv
(може містити додаткові дії, наприклад, формування заголовка таблиці). Або зробити загальний обробник помилок для всього API у файл
/api/error.js
. Такий підхід забезпечує більшу гнучкість і дозволяє скоротити розміри коду, однак, ми платимо за це відомими обмеженнями.

Розширення каталогів означають автоматичне віддачу з них вмісту певного типу, а значить, встановлення певних HTTP заголовків і перетворення результату в потрібний формат даних. Все це можна змінити вручну, але використання розширень скорочує кількість коду. З коробки " підтримуються такі розширення:
.json, .jsonp, .xml .ajax .csv, .ws, .sse
і цей список просто розширюємо за допомогою плагінів.

Простору імен

Всередині обробника видно такі імена, через які ми можемо звертатися до функцій IAS і підключеним бібліотек:
  • client — об'єкт, що містить распарсенные поля запиту, посилання на оригінальні запиту і відповіді, відповідно до
    client.req
    та
    client.res
    , API по роботі з запитом, посилання на сесію і авторизованого користувача;
  • application — об'єкт, який відповідає за додаток, і містить його конфігурацію, параметри і відповідне API сервера додатків;
  • db — неймспейс, що містить посилання на всі завантажені драйвери СУБД і встановлені з'єднання з базами даних, звертатися до них можна через db[alias] або db.alias;
  • api — неймспейс, що містить посилання на всі вбудовані і зовнішні бібліотеки, які були дозволені з конфігурації програми. Наприклад
    api.fs.readFile(...)
    або
    api.async.parallel(...);
  • api.impress — посилання на API сервера додатків;
  • системні глобальні ідентифікатори, загальноприйняті для JavaScript і Node.js
    require, console, Buffer, process, setTimeout, clearTimeout, setInterval, clearInterval, setImmediate, clearImmediate
    . Але ми можемо в конфігурації заборонити використання деяких з них, наприклад, відключивши додатком require, і надавши йому тільки певний набір бібліотек, автоматично завантажених в його неймспейс api.
Робити require в обробниках не потрібно, достатньо встановити бібліотеки в папку /impress через npm install і підключити їх через конфігурацію /сonfig/sandbox.js (спочатку в конфіги IAS, а потім локально в конфіги додатки). Далі бібліотеки видно в обробниках через
api.libName
, точно так само стають видні і вбудовані бібліотеки, наприклад,
api.path.extname(...)
і т. д.

Всі бази даних і драйвери СУБД видно через db.name. З'єднання налаштовуються в /config/databases.js (для кожного додатка окремо), встановлюються при старті і автоматично відновлюються при втраті зв'язку. У комплекті йдуть драйвери для MongoDB, PostgreSQL і MySQL, обгорнені в плагіни для IAS, при бажанні за 30 хвилин можна обернути в плагіни драйвера будь СУБД.

Для типу вмісту html використовується простий вбудований шаблонизатор, він потрібний скоріше не для повної генерації сторінок на стороні сервера, а для складання layout (основний розмітки і розташування шматків користувача), а так само, для підстановки чисельних значень структур даних в html. Шаблонизатор містить инклады і ітератори, але більш складну шаблонізації потрібно реалізовувати вже у браузері за допомогою React, Angular, EJS і т. д., запитуючи шаблони і дані окремо і збираючи їх в браузері (з переиспользованием шаблонів), що типово для динамічних веб-додатків. Вбудований шаблонизатор, починає рендеринг з файлу
html.template
і підставляє в нього дані з
client.context.data
. Конструкція
@fieldName@
підставить значення з поля, конструкція
@[file]@
вставить файл
file.template
, а конструкція
@[name]@ ... @[/name]@
реалізує ітератор по хешу або масиву з ім'ям name.

Для обробників, повертають серіалізовані дані.json, .jsonp, .csv і т. д.) шаблонизация не потрібна. Для них структура даних
client.context.data
просто серіалізуются в JSON (з відсікання рекурсії). Для зручності можна повертати структуру даних з обробника першим параметром
callback({ field: "value" });
Якщо один обробник повернув у callback дані або привласнив їх в
client.context.data
, то наступні за ним (до кінця життя поточного HTTP запиту) можуть читати і змінювати дані.

Обробники можуть змінювати http код статусу, додавати свої http заголовки, але в штатному режимі вони працюють тільки з об'єктом client, у якого є методи безпечного API:
client.error(code), client.download filePath, attachmentName, callback), client.cache(timeout), client.end(output)
і т. д. Починаючи з версії 0.1.157 в IAS реалізована часткова підтримка обробників middleware, що мають 3 параметри: req, res і next. Але потрібно це вкрай рідко, а код, портований з проектів express або connect, зазвичай можна переписати в кілька разів коротше і простіше.

Створювати обробники обох типів, тобто handler (з 2я параметрами) і middleware (з 3я параметрами) можна не тільки з файлів, а додаючи роутинг вручну, через виклики методів, наприклад:
application.get('/helloWorld.ajax', function(req, res, next) {
res.write('<h1>Middleware handler style</h1>');
next();
});

Структура програми

Серверний код не обмежується обробниками, додаток так само може містити модель предметної області, спеціалізовані бібліотеки і утиліти, використовувані в багатьох обробниках, і інші «місця», для розміщення логіки і даних. Всі додатки, що запускаються в IAS розміщуються в каталозі /applications і мають наступну структуру:
  • /app — кореневий каталог обробників, відповідний корені сайту
    http://hostname/
    ,
  • /config — конфігурація,
  • /doc — документація та допоміжні матеріали,
  • /files — каталог для розміщення завантажених користувачем файлів (у ньому автоматично будується 2-х або 3-х рівнева система підкаталогів, щоб не перевантажувати файлову систему великою кількістю файлів),
  • /init — код ініціалізації, що запускається при старті сервера (тут можна програмно створювати обробники, готувати структури даних в пам'яті, відкривати tcp порти, і т. д.),
  • /lib — каталог для бібліотек і утиліт, які завантажуються при старті (але після ініціалізації) і доступні з усього коду програми,
  • /log — каталог для логів (якщо в конфігурації налаштоване окреме логування для цього додатка),
  • /model — каталог для моделей предметної області (так само завантажується при старті, але після ініціалізації),
  • /setup — розміщені в цьому каталозі js-файли будуть запущені лише 1 раз при рестарті IAS або всього сервера, це місце необхідно для скриптів оновлень або міграції, які необхідні для підтримання повноцінного життєвого циклу програми вже під час його експлуатації,
  • /tasks — каталог для розміщення запланованих завдань, які бувають двох типів: відкриті приодически, через певний інтервал або запускаються в призначений час,
  • /tmp — каталог для тимчасових файлів.
У найближчих версіях з'являться ще такі каталоги (issue #195):
  • /client — каталог розміщення вихідного коду клієнтської частини,
  • /static — зібрані клієнти потім будуть поміщатися в
    /static
    , а в якості збирача можна буде використовувати декілька найбільш поширених засобів.

Функціонал IAS

Нехай ця стаття залишиться вступної, так що, я не буду зараз докладно описувати весь арсенал IAS і перевантажувати читача. Обмежуся простим перерахуванням основного: реєстрація сервісом (демоном), прозоре масштабування на багато процесів і багато серверів, вбудована система користувачів і сесій (у т. ч. анонімних і аутентифікованих), підтримка SSE (Server-Sent Events) та веб-сокетів з системою каналів і підписки на повідомлення, підтримка проксі серверів запитів, URL-реврайтінг, інтроспекція мережевого API і видача індексів каталогів, управління доступом до каталогів через access.js (аналог .htaccess), конфігурування програм, логування, прокручування логів, віддача статики з кешування в пам'ять, gzip компессия, підтримка HTTP заголовка «if-modified-since» HTTP 304 (Not Modified), підтримка HTTPS, стрімінг файлів з підтримкою віддачі по частинах (із зазначеного місця і до зазначеного місця, що зазвичай використовують плеєри, наприклад HTML5 video-тег через HTTP заголовки Content-Range та Accept-Ranges), є скрипти швидкого розгортання сервера для чистих машин (CentOS, Ubuntu, Debian), вбудовані механізми межпроцессового взаємодії через IPC, HTTP і ZeroMQ, спеціальне API для синхронізації стану між процесами, вбудований механізм моніторингу здоров'я серверів, підсистема запуску відкладених завдань, можливість породжувати воркеры (паралельні процеси), валідація структур даних і схем БД, генерація структур даних схем для SQL-сумісних баз даних, автоматична обробка помилок і довгого стека, оптимізація збору сміття, екранування пісочниць (sandboxes), підтримка HTTP basic authentication, обробка віртуальних хостів і віртуальних шляхів, приклеювання IP (sticky), плагіни (у т. ч. passport, geoip, nodemailer, минификации js, трансляції sass і т. д.), підсистема юніт-тестування, утиліти для upload/download файлів і багато чого іншого.

Висновок

Impress (IAS) активно розвивається, щотижня з'являється від 4 до 7 мінорних версій. Зараз актуальна версія 0.1.195 і на підході версія 0.2, в якій ми зафіксуємо структуру додатків і базова API, дотримуючись сумісність для всіх 0.2.x версій. У 0.2.x ми будемо займатися лише питаннями оптимізації і виправлення помилок, розширення функціональності буде можливо, тільки якщо це не зажадає редизайну додатків, заснованих на 0.2.x. Всі великі нововведення і експерименти будуть вводитися паралельно в гілці 0.3.x. Запрошую всіх бажаючих розвивати проект, а зі свого боку обіцяю підтримувати код, як мінімум, до тих пір, поки це актуально. Версія ж 1.0 з'явиться тільки тоді, коли я зрозумію, що незалежні розробники повністю в змозі підтримувати код. Зараз готується документація, яка до цього була неможлива з-за того, що структура і архітектура часто змінювалася, я опублікую посилання на неї готовності версії 0.2. До цього детальніше ознайомитися з IAS можна за прикладами, які встановлюються разом з IAS, як додаток за замовчуванням.

Трохи цифр станом на 2015-01-11: завантажень з npm вчора: 1 338, за цей тиждень: 5 997, за останній місяць: 21 223, зірок на github: 168, внесок в репозиторій: 8 осіб, рядків: 6 120, розмір исходников: 207 Кб (з них ядро: 118Кб), усереднена цикломатическая складність коду: 20, кількість закритих issues в github: 151, відкритих issues: 9, дата першої опублікованої версії: 2013-06-08, кількість вузлів у Travis CI: 233, кількість комітів github: 468.

Посилання

NPM: www.npmjs.com/package/impress
Github: github.com/tshemsedinov/impress

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.