Побудова MVC програми на Node.js з кластертзацией і виконанням коду в пісочниці

Добрий день, шановні читачі Хабра! Дана стаття розрахована на новачків, які тільки відкривають світ JS, яким є і я. У процесі вивчення і проектування сервера на Node.js розробник постійно стикається з необхідністю перезавантаження програми. А в разі, якщо над проектом працює кілька людей, отримуємо задоволену складну задачу.

Завдання — підняти сервер і обробляти декілька адрес, наприклад http://127.0.0.1/habr і http://127.0.0.1/habrahabr. Сервер повинен обробляти винятки, а також проект розрахований на високе навантаження.

Мета статті – розібратися, як створити высоконагруженное додаток, зручне для командної роботи і зрозуміле для новачків.

Перше, що необхідно зробити, це підняти сервер Node.js

var http = require('http');
var file = new static.Server('.');
http.createServer(function(req, res) {
file.serve(req, res);
}).listen(80);

Проблема в тому, що сервер працює тільки на одному процесі системи. Трохи переробимо код, додавши кластеризацію, для цього використовуємо стандартний модуль cluster:

const cluster = require('cluster');
const http = require('http');
const domain = require('domain');

const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', function(worker, code, signal){
console.log('worker' + worker.process.pid +' died');
cluster.fork();
});
cluster.on('online', function(worker) {
console.log('Worker' + worker.process.pid + 'is online');
});
} else {
http.createServer(function(req, res){
// Створюємо домен
var d = domain.create();
// Вішаємо обробник помилок, який поверне 500й статус і текст проблеми
d.on('error', function(err) {
res.statusCode = 500;
res.setHeader('content-type', 'text/plain');
res.end('Помилка!\n'+ err.stack);
});
// Додаємо наші змінні, які теж можуть згенерувати помилки самостійно
d.add(req);
d.add(res);
// Запускаємо потенційно небезпечний код всередині домену
d.run(function () {
var route_json = require('./application/route.json');
if( route_json[req.url] !== undefined){//Користувач вручну поставив контролер
console.log(route_json[req.url].controller);
}else{
url = urlapi.parse(decodeURI(req.url), true);//парсим url
url_arr = url.pathname.slice(1).split('/');//Перетворимо url в масив
}
res.end('hello world');
});
}).listen(3031).on('connection', function(socket) {
socket.setNoDelay(); // Відключаємо алгоритм Нагла.
});


}

З основним кодом сервера ми розібралися, тепер у нас є сервер з асинхронним обробником винятків, кластеризацией і обробкою url. Так як ми використовуємо парадигму MVC, то за еталон візьмемо codeigniter. Структура файлів виглядає наступним чином:

image

Опис структури:

  • app.js — головний код програми
  • core — повинна містити файли ядра програми, бібліотеки, модулі і т. п.
  • aplication — папка програми

    • controller — папка c контролерами
    • model — папка c моделями

    • model — папка c уявленнями
    • route.json — користувальницький роутинг
Потрібна обробка коду контролера. Для вирішення даної задачі існує кілька методів:

  • require — в даній публікації не розглядається.
  • eval — не рекомендований метод, по причині того що він працює в кілька разів повільніше ніж vm, до того ж це не самий безпечний метод
  • vm — це віртуальна машина, в якому код компілюється в пісочниці. Плюси даного методу в тому, що в разі витоку або проблем з роботою, можна знищувати не весь процес, а лише процес в пісочниці, але це вже окрема стаття.
З документації видно, що vm виконується в контексті, можна запустити в новому контексті або в поточному. Найбільш правильним варіантом рішення буде виконувати код в новому контексті.

Повний код прикладу:

const cluster = require('cluster');
const http = require('http');
const domain = require('domain');

const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', function(worker, code, signal){
console.log('worker' + worker.process.pid +' died');
cluster.fork();
});
cluster.on('online', function(worker) {
console.log('Worker' + worker.process.pid + 'is online');
});
} else {
http.createServer(function(req, res){
// Створюємо домен
var d = domain.create();
// Вішаємо обробник помилок, який поверне 500й статус і текст проблеми
d.on('error', function(err) {
res.statusCode = 500;
res.setHeader('content-type', 'text/plain');
res.end('Помилка!\n'+ err.stack);
});
// Додаємо наші змінні, які теж можуть згенерувати помилки самостійно
d.add(req);
d.add(res);
// Запускаємо потенційно небезпечний код всередині домену
d.run(function () {
var route_json = require('./application/route.json');
var fs = require('fs');//бібліотека для роботи з файлами
if( route_json[req.url] !== undefined){//Користувач вручну поставив контролер
var path = './application/controller/'+route_json[req.url].controller+'.js';
}else{
var urlapi = require('url');//Підключаємо бібліотеки для парсингу url
var url = urlapi.parse(decodeURI(req.url), true);//парсим url
var url_arr = url.pathname.slice(1).split('/');//Перетворимо url в масив
var path = './application/controller/'+url_arr[0]+'.js';
}
//Читаємо код контролера з папки
fs.readFile(path, 'utf8', 
function(err, code) {
var vm = require('vm');
var timestart = parseInt(new Date().getTime());
var pid = cluster.worker.process.pid;
var context = {
// -- підключаються об'єкти до контексту
pid:pid,
res:res,
req:req,
timestart:timestart,
require: require,
console: console
};
var vmContext = vm.createContext(context);
var script = vm.Script(code);
script.runInNewContext(vmContext);
});
});
}).listen(3031).on('connection', function(socket) {
socket.setNoDelay(); // Відключаємо алгоритм Нагла.
});


}

Приклад коду контролера:

res.setHeader('Access Control-Allow-Origin', '*');
res.setHeader('Access Control-Allow-Headers', 'origin, content-type, accept');
res.setHeader("Cache-Control", "no-cache, must-revalidate");
res.writeHead(200, {"Content-Type": "text/plain"});
res.write('CONTROLLER RUN');
res.end();

Таким чином, у нас є каркас додатку, який завантажує та виконує код без перезавантаження основного додатка (виконує код в пісочниці).

Дане рішення відмінно підійде для командної розробки великих програм. У даній статті ми розглянули cluster і vm, домени Node.js.

Посилання:

  1. learn.javascript.ru/ajax-nodejs
  2. nodejs.org/api/cluster.html
  3. ru.wikipedia.org/wiki/Model-View-Controller
  4. code-igniter.ru/user_guide/libraries/uri.html
  5. ru.wikipedia.org/wiki/JSON
  6. nodejs.org/api/domain.html
  7. nodejs.org/api/vm.html
  8. https://github.com/pan-alexey/nodeigniter — исходники на github
Джерело: Хабрахабр

0 коментарів

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