Основные требования
• легкость подключения модулей
• стандартная структура URL
• многоязычность
• автоматическое принятие изменений
• использование возможностей многопроцессорной системы
Система построена на базе Express. Для облегчения написания кода используется модуль wait.for
Файловая структура
- root
- routes
- mod_api
- test.js
- api.js
- mod_api
- views
- public
- app.js
- Server.js
- routes
Демонизация сервера под линукс
В нашем проекте был использован модуль forever.
-w — позволяет изменять модули без прямой перезагрузки сервера. Forever следит за изменениями и перегружает сервер по надобности.
-l ведет в пустоту, так как было решено, что два лога — это черезчур.
forever start -a -w -l /dev/null -o out.log -e err.log Server.js
Использование возможностей многопроцессорной системы
Запуск множества процессов посредством модуля «cluster» для распределения нагрузки между ядрами.
//файл Server.js
var cluster = require('cluster');
var workerCount = require('os').cpus().length;
cluster.setupMaster({ exec: "app.js" });
// Fork workers.
for (var i = 0; i < workerCount ; i++)cluster.fork()
Подключения модулей
Легкость подключения модулей достигается двумя включениями.
Автоматическое подключение всех routes
// part of app.js
// путь к routes относительно запускаемого файла
var routesPath = path.join(__dirname, 'routes');
//лист всех файлов в директории
var routeList = fs.readdirSync(routesPath);
for(var i in routeList){
var filePath = path.join(routesPath,routeList[i]);
if(fs.statSync(filePath).isFile() && path.extname(routeList[i])=='.js')
require(filePath)(app); // инициация путей
}
Подключение модуля и вызов функции
Routes дефинируются отдельно в каждом файле, чем так-же достигается унификация и простота подключения routes для конечного разработчика.
//файл api.js
module.exports = function (app) {
app.get('/api/:mod/:lang/:action', function(req, res){action(req,res,global.conf.METHODS.GET);});
app.post('/api/:mod/:lang/:action', function(req, res){action(req,res,global.conf.METHODS.POST);});
app.delete('/api/:mod/:lang/:action', function(req, res){action(req,res,global.conf.METHODS.DELETE);});
app.put('/api/:mod/:lang/:action', function(req, res){action(req,res,global.conf.METHODS.PUT);});
};
function action(req, res, method) {
//проверка языка
var lang = req.params.lang.toUpperCase();
if (global.conf.AVAILABLE_LANGUAGES.indexOf(lang) > -1) {
// чистка имени модуля
var mod = req.params.mod.replace(/[^a-zA-z0-9]+/g,'');
// чистка имени функции
var action = req.params.action.replace(/[^a-zA-z0-9]+/g,'');
// проверка существования модуля
fs.exists(path.resolve(__dirname, './mod_api/'+mod+".js"),function(ok){
if(ok){
// вызов модуля
var startMode = require('./mod_api/'+mod);
try{
// вызов функции с префиксом pub_
wait.launchFiber(startMode['pub_'+action], req, res, lang, method);
}catch(err){
res.status(405).pj(405,err.message,"Method Not Allowed");
}
}else{
res.status(503).pj(503,null,'Service Unavailable ');
}
});
}
else{
res.status(400).pj(400,null,'Not supported language');
}
}
Имплементация конечной функции
Все функции с префиксом pub_ являются публичными, все остальное приватное.
// file routes/mod_api/test.js
exports.pub_Start = function(req, res, lang, method){
res.pj(0,(method===global.conf.METHODS.POST)?"POST":"GET","SUCCESS ");
}
Унификация вывода
//part app.js
http.ServerResponse.prototype.pj = function(status,data,message){
try {
this.json({STATUS:status,CONTENT:data,MESSAGE:message});
}catch(e) {
console.error(e);
this.json({STATUS:999,CONTENT:null,MESSAGE:'parse response error'});}
};
Ну и в завершении вызов функции: api.codevit.net/api/test/en/Start
(я очень надеюсь, что сервер не обвалиться, это мой тестовый сервер)
Если кому-то будет интересно, могу выложить скелет на github.