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