📜 ⬆️ ⬇️

Building an MVC application on Node.js with clustering and code execution in a sandbox

Good afternoon, dear readers of Habr! This article is designed for beginners who are just discovering the world of JS, who I am. In the process of studying and designing the server on Node.js, the developer is constantly faced with the need to restart the application. And if there are several people working on the project, we get a satisfied complicated task.

The task is to raise the server and process several url, for example http://127.0.0.1/habr and http://127.0.0.1/habrahabr . The server must handle exceptions, and the project is designed for high loads.

The purpose of the article is to figure out how to create a hot swap application that is convenient for teamwork and understandable for beginners.

The first thing you need to do is raise the server to Node.js
')
var http = require('http'); var file = new static.Server('.'); http.createServer(function(req, res) { file.serve(req, res); }).listen(80); 

The problem is that the server works only on one process of the system. Let's slightly rework the code by adding clustering, for this we use the standard cluster module:

 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(); //   . }); } 

We dealt with the main server code, now we have a server with an asynchronous exception handler, clustering and url processing. Since we use the MVC paradigm, we take codeigniter as the standard. The file structure is as follows:

image

Structure Description:


Controller code processing is required. To solve this problem, there are several methods:


From the documentation you can see that vm runs in context, you can run in a new context, or in the current one. The most appropriate solution would be to execute code in a new context.

Full sample code:

 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(); //   . }); } 

Sample controller code:

 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(); 

Thus, we have an application framework that loads and executes the code without reloading the main application (executes the code in the sandbox).

This solution is perfect for team development of large applications. In this article, we looked at cluster and vm domains in Node.js.

References:

  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 - sources on github

Source: https://habr.com/ru/post/314186/


All Articles