📜 ⬆️ ⬇️

node-direct - one NodeJS server for several sites

tl; dr


With node-direct, you can upload server-side .js files and access them in the same way as .php scripts: example.com/foo.srv.js .


  1. Installation


    npm install -g node-direct 

  2. Nginx configuration


     location ~ \.srv\.js$ { root <path_to_website_files>; proxy_pass http://localhost:<port>; proxy_set_header X-Requested-File-Path $document_root$uri; } 

  3. Run


     node-direct --port=<port> 

  4. The script foo.srv.js, where req and res created by the express server.


     module.exports = function(req, res) { const someModule = require('some-module'); res.send('Hello world!'); } 


Introduction


When NodeJS became more or less popular, it was not easy for me to realize that everything is not as easy with it as with PHP. Using the latter one could create a .php file, upload it to the server, contact example.com/path/name.php and enjoy it. Such simplicity of script deployment was one of the reasons why "puff" became so popular.


In turn, NodeJS, regardless of the complexity of the application, forces many things to do by hand.



pm2 makes it easier to update and run the application after a reboot or after exceptions thrown by the application. But with him, firstly, you need to get your hand, and secondly, to force the launch of the next application to repeat the routine actions.


All of the above is rather an advantage than a disadvantage if you are developing a large application. But what if the application is very simple? What if this is not an application at all, but an ordinary site, with a small “apishka” (for example, a proxy for cross-domain requests)? What if a lot of sites are chasing a VPS, and for everyone setting up a NodeJS server is too lazy.


node-direct is a tool that allows you to deploy server-side JavaScript scripts as easy as .php :



In this case, a single node-direct instance can be used on different sites.


Configuration


For most of the "magic" is the nginx config. It needs to be taught to handle requests to .srv.js files (you can use any extension).


 location ~ \.srv\.js$ { root <path_to_website_files>; proxy_pass http://localhost:<port>; proxy_set_header X-Requested-File-Path $document_root$uri; } 


An example of a complete config:


 server { listen 80; server_name example.com; # Serve static files location / { root /var/web/example.com/public; index index.srv.js index.html index.htm; } location ~ \.srv\.js$ { root /var/web/example.com/public; proxy_pass http://localhost:8123; proxy_set_header X-Requested-File-Path $document_root$uri; } } 

That is, statics will be served using nginx (this is known to be several times faster than statics that express not need to be cached), plus, even .php can be added to the list of supported server files (usable for legacy projects).


Using


 node-direct --port=8000 

Node-direct uses the good old Express . Server files must export a function that accepts request and response .


Hello world:


 module.exports = function(req, res) { const someModule = require('some-module'); res.send('Hello world!'); } 

JSON API:


 module.exports = function(req, res) { if(req.method === 'POST') { req.json({ message: 'Everything is awesome' }); } else { req.status(400).json({ message: 'Only POST requests are allowed' }); } } 

Rendering


 const fs = require('fs'); const ejs = require('ejs'); const template = ejs.compile(fs.readFileSync('./templates/index.html')); module.exports = function(req, res) { res.type('html').send(template({ foo: 'bar' })); } 

For more information, see the Express documentation.


An example of a hypothetical application:


 /package.json -  dependencies and devDependencies     /index.html -  HTML  /js/app.js - client-side JavaScript /css/style.css -  /node_modules/ -      "npm install" (     ) /foo/index.srv.js - JSON API,     /foo/ /bar/index.srv.js -  HTML ,      /bar/ 

Flags


Are common


--port - node-direct server port (8123 by default)


Standalone mode


In this mode, an HTTP server is created that distributes statics and does not require nginx. The mode is used by the developer to run on a local machine.


--standalone - turns on standalone mode
--root - path to static files ( process.cwd() by default)
--ext - extension of server js files ( .srv.js by default)


 node-direct --port=8000 --standalone --root=./foo/bar --ext=.serverside.js 

Run node-direct after restarting the server (only needed once)


You can add a new task to crontab, call crontab -e and add a new task to the file.


 @reboot <path_to_node> <path_to_installed_module> [<flags>] 


Example:


 @reboot /usr/local/bin/node /usr/local/lib/node_modules/node-direct/index.js --port=8000 

Problems


Caching Requested Modules


As you know, NodeJS caches the values ​​that the require function returns. When require('foo') is called two or more times, the function returns the same value. node-direct updates the cache automatically when the .srv.js file is changed (for example, you uploaded a new file to the server) and there is no need to restart node-direct . A problem may occur when .srv.js requests other modules.


 // foo.srv.js module.exports = function(req, res) { const bar = require('./bar'); // ... } 

When foo.srv.js changes, the cache is updated as expected, but when ./bar changes, its value remains the same. A node-direct could update all requested modules on its own, but this would cause side effects with unpredictable behavior in other modules. This problem can be solved by creating yesterday, which cleans the cache when the specified module is updated.


In the example below, hot swap is enabled for ./bar , but not for ./baz .


 // foo.srv.js //       ,    const fs = require('fs'); const barPath = require.resolve('./bar'); const watcher = fs.watch(barPath, (eventType) => { if (eventType === 'change') { delete require.cache[barPath]; watcher.close(); } }); module.exports = function(req, res) { const bar = require('./bar'); const baz = require('./baz'); // ... } 

If it looks too hard, use a small fresh-up module that does the same.


 // foo.srv.js //       ,    const freshUp = require('fresh-up'); freshUp(require.resolve('./bar'); module.exports = function(req, res) { const bar = require('./bar'); const baz = require('./baz'); // ... } 

Potential vulnerability: X-Requested-File-Path header


As you may have noticed, nginx sends the address of the requested node-direct file to the server as an X-Requested-File-Path header. Using this header, an attacker can invoke arbitrary javascript files on your server. In order to do something bad, a hacker needs to know the address of the "dangerous" file, relative to the root. To close the vulnerability, it is necessary to prohibit access to the node-direct port from the outside.


Here's how to do this in Linux using the ufw firewall .


 sudo ufw deny 8123 

')

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


All Articles