59

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.

Reporter
  • 3,897
  • 5
  • 33
  • 47
  • 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 – Raynos Oct 18 '11 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. – Joseph Yaduvanshi Oct 18 '11 at 17:07
  • 5
    it's not silly at all. it allows you to build skeletons with core functionality that others can leverage – ekkis Feb 11 '19 at 02:44
  • 3
    @Raynos you call it silly but that's what Ryan Dahl chose to do for Deno – flow Oct 29 '20 at 07:59

6 Answers6

41

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.

nikc.org
  • 16,462
  • 6
  • 50
  • 83
Phillip Kovalev
  • 2,479
  • 22
  • 23
  • 2
    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*. – Dan Tao Jan 29 '14 at 16:43
  • 1
    @DanTao Сounterquestion: why execution of remote, not your own distributed and tested code in your app runtime can be good idea? If remote system is out of your own/company control, then it is not secure by default, imho. If remote system out of your own/company network – it is not secure due to many opportunities to compromise code in the middle. In other case, if remote system is under your control, why you need to request, transfer and execute code instead of transferring the data over the network and sharing code to work with using node.js modules? – Phillip Kovalev Jan 29 '14 at 20:11
  • 11
    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! – Dan Tao Jan 29 '14 at 20:17
  • "maybe they aren't good reasons, but they are reasons" – it's what i has called "bad practice". Anybody can find a reasons for killing kittens, must i stop to call them "bad"? In any case, i'd edited the answer to soften tone of the my opinion. – Phillip Kovalev Jan 29 '14 at 20:34
  • 14
    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 `wget`ted 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?:// – Már Örlygsson Jun 06 '14 at 09:10
  • @MárÖrlygsson «A file's trustworthiness should not be measured merely by whether you access it via FILE:// or HTTPS?://» and it's why i note «only if you trust to the remote service and the network between». – Phillip Kovalev Jun 07 '14 at 19:06
  • 3
    So, don't require anything from "github" in the production app ;) – Phillip Kovalev Jun 07 '14 at 19:17
  • 2
    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). – rainabba May 30 '16 at 20:07
12

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");
4b0
  • 21,981
  • 30
  • 95
  • 142
7

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

William Leung
  • 1,556
  • 17
  • 26
  • 1
    I think you should use 'application/javascript' instead of 'text/javascript' in `if (res.statusCode === 200 && /^application\/javascript/.test(res.headers['content-type']))` – Paul Verschoor Apr 14 '20 at 12:46
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
});
reubano
  • 5,087
  • 1
  • 42
  • 41
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)

Olivier Lalonde
  • 19,423
  • 28
  • 76
  • 91
  • 2
    Sadly [require.extensions](https://nodejs.org/api/all.html#all_require_extensions) has been deprecated... – Domi May 02 '15 at 14:55
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);
    },
  );
  • 4
    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. – SteppingHat Feb 26 '20 at 04:52