6

我需要允许我的应用程序的用户使用 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;
    }
});

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

任何帮助是极大的赞赏!!

编辑:

在深入研究之后,我注意到尝试将响应对象转换为 JSON 对象会导致循环结构错误。

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

因此,使用 Meteor-Router 解决方案时,响应对象会发生一些事情。我还注意到“数据”事件 response.finished 被标记为 true。

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

1 回答 1

1

Meteor 路由器安装了一个中间件来进行路由。所有 Connect 中间件要么必须调用next()(恰好一次)以指示响应尚未确定,要么必须通过调用res.end()或通过管道传递给响应来确定响应。不允许两者都做。

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

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

然而,通过管道响应,我们已经解决了响应。Meteor 路由器不应该调用next()nor res.end()

我们通过分叉 Meteor 路由器并做一些小改动解决了这个问题。我们将第else87 行(之后if (output === false))替换为:

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

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

这种方式return;在路由方法中会告诉路由器什么都不做。当然,您已经通过管道解决了响应。


中间件的源代码与 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();
  });
于 2013-07-21T22:01:48.577 回答