Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use readFileSync inside nodeJs vm

Tags:

node.js

I'm experimenting with nodeJs vm. This code works :

server.js

    var fs = require('fs');
    var vm = require('vm');

    var app = fs.readFileSync(__dirname + '/' + 'app.js');
    vm.runInThisContext(app);


    var http = require('http');

    var server = http.createServer(onRequest);
    server.listen(8080, '127.0.0.1');

app.js

    function onRequest(req, res) {
        res.writeHead(200, {'Content-Type': 'text/html'});
        res.end('Hello Node.js\n');
    }

Now if I change app.js into

  function onRequest(req, res) {
      res.writeHead(200, {'Content-Type': 'text/html'});
      res.end(fs.readFileSync(__dirname + '/index.html'));
  }

It does not work anymore : browser would print "This webpage is not available"

How to make it work maybe by somehow binding fs.readFileSync to the local context of onRequest ?

like image 448
user310291 Avatar asked Mar 31 '15 16:03

user310291


3 Answers

While the answer posted by JohnKiller is technically correct, I would like to point out a solution that uses vm.runInContext and is more robust in my opinion.

// app.js
var fs = require('fs');
module.exports = function onRequest(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.end(fs.readFileSync(__dirname + '/index.html'));
}

// server.js
var appFile = __dirname + '/' + 'app.js';
var app = fs.readFileSync(appFile);   

var context = vm.createContext({
  __filename: appFile,
  __dirname: __dirname,
  require: require,
  module: { exports: {} }
});
context.exports = context.module.exports;

vm.runInContext(app, context, { filename: appFile });
var onRequest = context.module.exports;

var http = require('http');   
var server = http.createServer(onRequest);
server.listen(8080, '127.0.0.1');

I see the following major benefits:

1) The global context is not polluted with additional variables used only by the loaded script file.

2) The code loaded from the external file is sandboxed, it cannot directly change the scope variables of the caller. It is very clear what variables are available to the external file.

3) The code in the external file is self-encapsulated and does not depend on any external modules being provided in the context. In fact, it is a regular Node.js file that can be loaded directly via require('./app.js')

like image 65
Miroslav Bajtoš Avatar answered Sep 23 '22 11:09

Miroslav Bajtoš


Quoting from the docs:

Running code does not have access to local scope, but does have access to the current global object.

So, variable __dirname and module fs are undefined in that context.

To solve the problem, use the global object:

server.js

var fs = require('fs');
var vm = require('vm');

global.fs = fs;
global.__dirname = __dirname;

var app = fs.readFileSync(__dirname + '/' + 'app.js');
vm.runInThisContext(app);


var http = require('http');

var server = http.createServer(onRequest);
server.listen(8080, '127.0.0.1');

app.js

function onRequest(req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(global.fs.readFileSync(global.__dirname + '/index.html'));
}
like image 25
JohnKiller Avatar answered Sep 24 '22 11:09

JohnKiller


wtf, why?

var app = fs.readFileSync(__dirname + '/' + 'app.js');
vm.runInThisContext(app);

just do:

require('./app.js')

Sooo you entire app looks like:

server.js

var http = require('http');
// use './' for relative paths
var app = require('./app.js');
// load app.js
var server = http.createServer(app);
server.listen(8080, '127.0.0.1');

app.js

// app.js has to load `fs` for itself
var fs = require('fs');
module.exports = function app(req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(global.fs.readFileSync('./index.html'));
}

Done

But, if for some reason you REALLY want to read, compile and run app.js with vm instead of simple old require

server.js

var http = require('http');
// use './' for relative paths
var app = vm.runInThisContext('./app.js');
// load app.js
var server = http.createServer(app);
server.listen(8080, '127.0.0.1');

app.js

// app.js _still_ has to load `fs` for itself
var fs = require('fs');

// last value is returned
function app(req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(global.fs.readFileSync('./index.html'));
}
like image 29
lyjackal Avatar answered Sep 22 '22 11:09

lyjackal