如何使用文件系统的createReadStream与Meteor路由器(NodeJS)

时间:2013-06-12 19:11:53

标签: node.js meteor meteorite

我需要允许我的应用程序的用户使用Meteor下载文件。目前我所做的是当用户请求将我输入的文件下载到Mongo中的“fileRequests”集合时,该文档包含文件位置和请求的时间戳,并返回新创建的请求的ID。当客户端获得新ID时,它会立即转到mydomain.com/uploads/:id。然后我在Meteor之前使用这样的东西拦截请求:

var connect = Npm.require("connect");
var Fiber = Npm.require("fibers");
var path = Npm.require('path');
var fs = Npm.require("fs");
var mime = Npm.require("mime");

__meteor_bootstrap__.app
    .use(connect.query())
    .use(connect.bodyParser()) //I add this for file-uploading
    .use(function (req, res, next) {
        Fiber(function() {

            if(req.method == "GET") {
                // get the id here, and stream the file using fs.createReadStream();
            }
            next();
        }).run();
    });

我检查确保文件请求的发生时间不到5秒,并在查询后立即删除了请求文档。

我觉得这很有效,而且很安全(足够)。没有人登录就没有人可以提出请求.5秒是一个非常小的窗口,可以让某人能够劫持创建的请求URL,但我对我的解决方案感觉不对。感觉很脏!

所以我试图用Meteor-Router来完成同样的事情。通过这种方式,我可以检查他们是否正确登录而没有对世界诡计开放5秒钟。

所以这是我为此写的代码:

    Meteor.Router.add('/uploads/:id', function(id) {

    var path = Npm.require('path');
    var fs = Npm.require("fs");
    var mime = Npm.require("mime");

    var res = this.response;

    var file = FileSystem.findOne({ _id: id });

    if(typeof file !== "undefined") {
        var filename = path.basename(file.filePath);
        var filePath = '/var/MeteorDMS/uploads/' + filename;

        var stat = fs.statSync(filePath);

        res.setHeader('Content-Disposition', 'attachment; filename=' + filename);
        res.setHeader('Content-Type', mime.lookup(filePath));
        res.setHeader('Content-Length', stat.size);

        var filestream = fs.createReadStream(filePath);

        filestream.pipe(res);

        return;
    }
});

这看起来很棒,适合其他代码并且易于阅读,不涉及黑客攻击,但是!它不起作用!浏览器旋转和旋转,从来不知道该怎么做。我有ZERO错误消息。我可以继续在其他标签上使用该应用。我不知道它在做什么,它永远不会停止“加载”。如果我重新启动服务器,我会得到一个包含所有正确标题的0字节文件,但是我没有得到数据。

非常感谢任何帮助!!

编辑:

在进行了一些挖掘之后,我注意到尝试将响应对象转换为JSON对象会导致循环结构错误。

现在有趣的是,当我收听文件流中的“数据”事件,并尝试对响应对象进行字符串化时,我没有收到该错误。但如果我尝试在我的第一个解决方案中做同样的事情(听“数据”并将响应字符串化),我会再次收到错误。

因此,使用Meteor-Router解决方案会对响应对象产生一些影响。我还注意到,在“data”事件中,response.finished被标记为true。

filestream.on('data', function(data) {
    fs.writeFile('/var/MeteorDMS/afterData', JSON.stringify(res));
});

1 个答案:

答案 0 :(得分:1)

Meteor路由器安装中间件来进行路由。所有Connect中间件都必须调用next()(恰好一次)以指示响应尚未解决或必须通过调用res.end()或通过管道响应来解决响应。不允许两者兼顾。

我研究了中间件的源代码(见下文)。我们看到我们可以返回false告诉中间件调用next()。这意味着我们声明此路由没有解决响应,我们希望让其他中间件完成他们的工作。

或者我们可以返回模板名称,文本,数组[status, text]或数组[status, headers, text],中间件将通过使用数据调用res.end()代表我们解决响应我们回来了。

但是,通过对响应的管道,我们已经确定了响应。 Meteor路由器不应该调用next()res.end()

我们通过分支Meteor路由器并进行一些小改动来解决这个问题。我们将第87行(else之后)中的if (output === false)替换为:

else if (typeof(output)!="undefined") {

在我的fork中查看带有sha 8d8fc23d9c的提交。

这种方式return;路由方法会告诉路由器执行 nothing 。当然,你已经通过管道确定了响应。


使用sha f910a090ae提交的中间件的源代码:

// hook up the serving
__meteor_bootstrap__.app
  .use(connect.query()) // <- XXX: we can probably assume accounts did this
  .use(this._config.requestParser(this._config.bodyParser))
  .use(function(req, res, next) {
    // need to wrap in a fiber in case they do something async
    // (e.g. in the database)
    if(typeof(Fiber)=="undefined") Fiber = Npm.require('fibers');

    Fiber(function() {
      var output = Meteor.Router.match(req, res);

      if (output === false) {
        return next();
      } else {
        // parse out the various type of response we can have

        // array can be
        // [content], [status, content], [status, headers, content]
        if (_.isArray(output)) {
          // copy the array so we aren't actually modifying it!
          output = output.slice(0);

          if (output.length === 3) {
            var headers = output.splice(1, 1)[0];
            _.each(headers, function(value, key) {
              res.setHeader(key, value);
            });
          }

          if (output.length === 2) {
            res.statusCode = output.shift();
          }

          output = output[0];
        }

        if (_.isNumber(output)) {
          res.statusCode = output;
          output = '';
        }

        return res.end(output);
      }
    }).run();
  });