Добрый день, уважаемые читатели Хабра! Данная статья рассчитана на новичков, которые только открывают мир JS, коим являюсь и я. В процессе изучения и проектирования сервера на Node.js разработчик постоянно сталкивается с необходимостью перезагрузки приложения. А в случае, если над проектом работает несколько человек, получаем довольную сложную задачу.
Задача — поднять сервер и обрабатывать несколько url, например 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. Структура файлов выглядит следующим образом:

Описание структуры:
- 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.
Ссылки:
- learn.javascript.ru/ajax-nodejs
- nodejs.org/api/cluster.html
- ru.wikipedia.org/wiki/Model-View-Controller
- code-igniter.ru/user_guide/libraries/uri.html
- ru.wikipedia.org/wiki/JSON
- nodejs.org/api/domain.html
- nodejs.org/api/vm.html
- https://github.com/pan-alexey/nodeigniter — исходники на github
ссылка на оригинал статьи https://habrahabr.ru/post/314186/
Добавить комментарий