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

我需要允许我的应用程序的用户下载与meteor文件。 目前我所做的是当用户请求下载一个文件到我在Mongo的“fileRequests”集合中input文件位置和请求的时间戳,并返回新创build的请求的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秒钟之前完成的,我在查询完文件后立即删除请求文件。

这是有效的,我认为是安全的(足够)。 没有人可以login请求,5秒是一个非常小的窗口,能够强制创build请求的URL,但我只是不适合我的解决scheme。 感觉很脏!

所以我试图用Meteor-Router来完成同样的事情。 这样,我可以检查他们是否正确地login了,而没有向世界展示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对象会导致循环结构错误。

现在有趣的是,当我听文件stream的“数据”事件,并试图串化响应对象,我不明白这个错误。 但是,如果我试图在我的第一个解决scheme中做同样的事情(听“数据”和串化响应),我再次得到错误。

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

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

Meteor路由器安装一个中间件来完成路由。 所有连接中间件必须调用next() (正好一次)来表示响应还没有被解决,或者必须通过调用res.end()或通过pipe道响应来解决响应。 这是不允许的。

我研究了中间件的源代码(见下文)。 我们看到我们可以返回false来告诉中间件调用next() 。 这意味着我们宣布这条路线没有解决答复,我们希望让其他中间件做好工作。

或者,我们可以返回一个模板名称,一个文本,一个数组[status, text]或者一个数组[status, headers, text] ,中间件将通过调用res.end()回。

但是,通过回答,我们已经解决了答复。 Meteor路由器不应该调用next()res.end()

我们通过分streamMeteor路由器并做出一些小的改变来解决问题。 我们用第87行replace了else (在if (output === false) ):

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

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

这种方式return; 在路由方法中会告诉路由器什么都不做。 当然,你已经通过pipe道来解决这个问题了。


中间件的源代码,如在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 // (eg 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(); });