63

Is there a standard way to require a Node module located at some URL (not on the local filesystem)?

Something like:

require('http://example.com/nodejsmodules/myModule.js');

Currently, I am simply fetching the file into a temporary file, and requiring that.

4
  • 6
    You realise relying on a remote HTTP server to consistantly give you source code is silly. And then trusting the remote HTTP server to not give you insecure code is just beyond ridiculious Commented Oct 18, 2011 at 16:15
  • 1
    If anything, you should provide some mechanism to prevent a man-in-the-middle attack or fetch all files over https, which will make fetching slower. Commented Oct 18, 2011 at 17:07
  • 5
    it's not silly at all. it allows you to build skeletons with core functionality that others can leverage Commented Feb 11, 2019 at 2:44
  • 4
    @Raynos you call it silly but that's what Ryan Dahl chose to do for Deno Commented Oct 29, 2020 at 7:59

6 Answers 6

43

You can fetch module using http.get method and execute it in the sandbox using vm module methods runInThisContext and runInNewContext.

Example

var http = require('http')
  , vm = require('vm')
  , concat = require('concat-stream'); // this is just a helper to receive the
                                       // http payload in a single callback
                                       // see https://www.npmjs.com/package/concat-stream

http.get({
    host: 'example.com', 
    port: 80, 
    path: '/hello.js'
  }, 
  function(res) {
    res.setEncoding('utf8');
    res.pipe(concat({ encoding: 'string' }, function(remoteSrc) {
      vm.runInThisContext(remoteSrc, 'remote_modules/hello.js');
    }));
});

IMO, execution of the remote code inside server application runtime may be reasonable in the case without alternatives. And only if you trust to the remote service and the network between.

Sign up to request clarification or add additional context in comments.

8 Comments

I'm upvoting for the information, but I do wish that instead of just saying "it's really bad practice" you bothered to explain why.
1. Maybe the code is available via HTTPS in a widely-trusted CDN or through a trusted partner. 2. Maybe the code is available via HTTPS at a remote internal location. 3. Maybe the code is packaged and distributed internally in such a way that access to the FS where it's ultimately executed is blocked and so npm cannot be used. These are just off the top of my head; maybe they aren't good reasons, but they are reasons. And anyway, my point was simply that it's more useful to explain the rationale for an opinion than to label it "bad practice" and say nothing more. Which your comment now does!
In this day and age, HTTP-GET should be considerd a perfectly valid method of referencing/opening a file. Implying that files are somehow more secure because you wgetted them to your local hard-drive first, is very much akin to security through obscurity. A file's trustworthiness should not be measured merely by whether you access it via FILE:// or HTTPS?://
So, don't require anything from "github" in the production app ;)
Thanks for the answer. As for the "execution of remote code"; these days, most node apps are initialized with npm install which pulls from the web. How is that any different than what the op seeks to do in spirit? There's nothing bad practice about the idea in general, but anything can be done poorly; creating security risks. I for one am looking to do this very thing to manage my config script (it's not JUST json as it requires more intelligence) and I've got rotating keys to protect the requests, which nobody would know about but the app anyway (more secure than npm modules).
|
14

Install the module first :

npm install require-from-url

And then put in your file :

var requireFromUrl = require('require-from-url/sync');
requireFromUrl("http://example.com/nodejsmodules/myModule.js");

3 Comments

this does not seem to work if the script is served gzipped
Wow, this works exactly as desired. Thanks
This approach unfortunately does not work on environments like AWS (Lambda), because internally it starts node process to fetch url content and this is not allowed in such an environments.
8

0 dependency version (node 6+ required, you can simply change it back to ES5)

const http = require('http'), vm = require('vm');

['http://example.com/nodejsmodules/myModule.js'].forEach(url => {
    http.get(url, res => {
        if (res.statusCode === 200 && /\/javascript/.test(res.headers['content-type'])) {
            let rawData = '';
            res.setEncoding('utf8');
            res.on('data', chunk => { rawData += chunk; });
            res.on('end', () => { vm.runInThisContext(rawData, url); });
        }
    });
});

It is still the asynchronous version, if sync load is the case, a sync http request module for example should be required

1 Comment

I think you should use 'application/javascript' instead of 'text/javascript' in if (res.statusCode === 200 && /^application\/javascript/.test(res.headers['content-type']))
3

If you want something more like require, you can do this:

var http = require('http')
  , vm = require('vm')
  , concat = require('concat-stream') 
  , async = require('async'); 

function http_require(url, callback) {
  http.get(url, function(res) {
    // console.log('fetching: ' + url)
    res.setEncoding('utf8');
    res.pipe(concat({encoding: 'string'}, function(data) {
      callback(null, vm.runInThisContext(data));
    }));
  })
}

urls = [
  'http://example.com/nodejsmodules/myModule1.js',
  'http://example.com/nodejsmodules/myModule2.js',
  'http://example.com/nodejsmodules/myModule3.js',
]

async.map(urls, http_require, function(err, results) {
  // `results` is an array of values returned by `runInThisContext`
  // the rest of your program logic
});

Comments

2

You could overwrite the default require handler for .js files:

require.extensions['.js'] = function (module, filename) {
    // ...
}

You might want to checkout better-require as it does pretty much this for many file formats. (I wrote it)

1 Comment

Sadly require.extensions has been deprecated...
0

  const localeSrc = 'https://www.trip.com/m/i18n/100012631/zh-HK.js';
  const http = require('http');
  const vm = require('vm');
  const concat = require('concat-stream');
  http.get(
    localeSrc,
    res => {
      res.setEncoding('utf8');
      res.pipe(
        concat({ encoding: 'string' }, remoteSrc => {
          let context = {};
          const script = new vm.Script(remoteSrc);
          script.runInNewContext(context);
          console.log(context);
        }),
      );
    },
    err => {
      console.log('err', err);
    },
  );

1 Comment

Hi! Welcome to SO. When posting answers, please be sure to provide an explanation as to how your solution works and how it solves the issue.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.