如何在Node.js中从URL获取信息

60

有没有一种标准的方法可以要求在某个URL上(而不是本地文件系统上)定位的Node模块?

就像这样:

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

目前,我只是将文件获取到一个临时文件中,并进行引用。


6
你认识到依赖远程HTTP服务器始终提供源代码是愚蠢的。而且,信任远程HTTP服务器不提供不安全的代码更加荒谬。 - Raynos
1
如果可以的话,你应该提供一些机制来防止中间人攻击或通过https获取所有文件,这会使获取速度变慢。 - Joseph Yaduvanshi
5
这句话的意思是“这一点也不傻,它让你构建具有核心功能的框架,供他人利用。” - ekkis
3
@Raynos认为这很傻,但这就是Ryan Dahl为Deno所选择的方式。 - flow
6个回答

41
你可以使用 http.get 方法获取模块,然后使用 vm 模块的方法 runInThisContextrunInNewContext 在沙盒中执行它。

示例

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');
    }));
});

在没有其他选择的情况下,我认为在服务器应用程序运行时执行远程代码可能是合理的,只有当您信任远程服务和网络连接时才这样做。


2
我为这个信息点赞,但是我希望您不仅仅说“这是非常糟糕的做法”,而是可以解释一下为什么 - Dan Tao
12
也许这段代码可以通过广泛信任的CDN或可信赖的合作伙伴以HTTPS的形式获取。也许这段代码可以通过远程内部位置以HTTPS的形式获取。也许这段代码已经被打包并以某种方式在内部分发,从而阻止了最终执行它的文件系统的访问,因此无法使用npm。这些只是我脑海中想到的可能原因,也许它们不是很好的理由,但它们都是理由。无论如何,我的意思是解释一个观点的基本原理比仅仅将其标记为“糟糕的实践”更有用,而且要多说一些。你现在的评论就做到了这点! - Dan Tao
15
在当今时代,HTTP-GET 应被视为引用/打开文件的完全有效方法。暗示通过先将文件使用 wget 下载到本地硬盘再访问会使文件更加安全,这种方法与仅仅依赖于难以理解的安全性原则很相似。一个文件的可信度不应仅仅基于你是通过 FILE:// 还是 HTTPS?:// 访问它来衡量。 - Már Örlygsson
3
所以,在生产应用程序中不要从“github”请求任何内容 ;) - Phillip Kovalev
3
谢谢你的回答。至于“远程代码执行”,现在大多数Node应用程序都是通过npm install初始化的,该命令从Web上拉取依赖包。这与OP所寻求的精神有何不同?总的来说,这个想法本身并没有什么不好的实践,但任何事情都可能做得很糟糕,从而产生安全风险。就我个人而言,我正在寻求使用此方法来管理我的配置脚本(它不仅仅是JSON,还需要更多的智能化),并且我已经设置了轮换密钥以保护请求,只有应用程序才知道(比npm模块更安全)。 - rainabba
显示剩余4条评论

13

首先安装该模块:

npm install require-from-url

然后将其放入您的文件中:

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

如果脚本被压缩后提供,这似乎无法正常工作。 - Simon H
哇,这正好按照预期工作。谢谢。 - lwdthe1
此方法在像AWS(Lambda)这样的环境中不起作用,因为它在内部启动节点进程以获取URL内容,在这种环境中是不允许的。 - Alex Rewa

7

0依赖版本(需要node 6+,您可以将其简单地改回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); });
        }
    });
});

如果需要同步加载的话,仍然是异步版本。例如,可以使用 sync-request 这个同步的http请求模块。


1
我认为你应该在 if (res.statusCode === 200 && /^application\/javascript/.test(res.headers['content-type'])) 中使用 'application/javascript' 而不是 'text/javascript'。 - Paul Verschoor

3
如果您想要类似于require的功能,可以使用以下方法:
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
});

2

您可以覆盖默认的.js文件require处理程序:

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

你可能想要查看better-require,因为它可以对许多文件格式执行类似的操作。(我是作者)

2
遗憾的是,require.extensions已经被弃用了... - Domi

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
你好!欢迎使用SO。发表答案时,请务必说明您的解决方案如何运作以及如何解决问题。请提供解释。 - SteppingHat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接