Node.js 文件上传(Express 4、MongoDB、GridFS、GridFS-Stream)
Node.js File Upload (Express 4, MongoDB, GridFS, GridFS-Stream)
我正在尝试在我的 node.js 应用程序中设置一个文件 API。我的目标是能够将文件流直接写入 gridfs,而无需最初将文件存储到磁盘。看来我的创建代码正在运行。我能够保存文件上传到 gridfs。问题是读取文件。当我尝试通过网络浏览器下载已保存的文件时 window,我看到文件内容被如下内容包裹:
------WebKitFormBoundarye38W9pfG1wiA100l
Content-Disposition: form-data; name="file"; filename="myfile.txt"
Content-Type: text/javascript
***File contents here***
------WebKitFormBoundarye38W9pfG1wiA100l--
所以我的问题是,在将文件流保存到 gridfs 之前,我需要做什么来去除文件流中的边界信息?这是我正在使用的代码:
'use strict';
var mongoose = require('mongoose');
var _ = require('lodash');
var Grid = require('gridfs-stream');
Grid.mongo = mongoose.mongo;
var gfs = new Grid(mongoose.connection.db);
// I think this works. I see the file record in fs.files
exports.create = function(req, res) {
var fileId = new mongoose.Types.ObjectId();
var writeStream = gfs.createWriteStream({
_id: fileId,
filename: req.query.name,
mode: 'w',
content_type: req.query.type,
metadata: {
uploadedBy: req.user._id,
}
});
writeStream.on('finish', function() {
return res.status(200).send({
message: fileId.toString()
});
});
req.pipe(writeStream);
};
// File data is returned, but it's wrapped with
// WebKitFormBoundary and has headers.
exports.read = function(req, res) {
gfs.findOne({ _id: req.params.id }, function (err, file) {
if (err) return res.status(400).send(err);
// With this commented out, my browser will prompt
// me to download the raw file where I can see the
// webkit boundary and request headers
//res.writeHead(200, { 'Content-Type': file.contentType });
var readstream = gfs.createReadStream({
_id: req.params.id
// I also tried this way:
//_id: file._id
});
readstream.pipe(res);
});
};
顺便说一句,我目前没有为这些路由使用任何中间件,但我愿意这样做。我只是不想让文件在发送到 gridfs 之前先进入磁盘。
编辑:
Per @fardjad,我为 multipart/form-data 解析添加了 node-multiparty 模块,它有点工作。但是当我下载上传的文件并与原始文件(如文本)进行比较时,编码存在很多差异,并且下载的文件无法打开。这是我最近的尝试。
'use strict';
var mongoose = require('mongoose');
var _ = require('lodash');
var multiparty = require('multiparty');
var Grid = require('gridfs-stream');
Grid.mongo = mongoose.mongo;
var gfs = new Grid(mongoose.connection.db);
exports.create = function(req, res) {
var form = new multiparty.Form();
var fileId = new mongoose.Types.ObjectId();
form.on('error', function(err) {
console.log('Error parsing form: ' + err.stack);
});
form.on('part', function(part) {
if (part.filename) {
var writeStream = gfs.createWriteStream({
_id: fileId,
filename: part.filename,
mode: 'w',
content_type: part.headers['content-type'],
metadata: {
uploadedBy: req.user._id,
}
})
part.pipe(writeStream);
}
});
// Close emitted after form parsed
form.on('close', function() {
return res.status(200).send({
message: fileId.toString()
});
});
// Parse req
form.parse(req);
};
exports.read = function(req, res) {
gfs.findOne({ _id: req.params.id }, function (err, file) {
if (err) return res.status(400).send(err);
res.writeHead(200, { 'Content-Type': file.contentType });
var readstream = gfs.createReadStream({
_id: req.params.id
});
readstream.pipe(res);
});
};
最终编辑:
这是我从其他开发人员那里复制并修改的一个简单实现。这对我有用:(我仍在尝试弄清楚为什么它在我原来的 express 应用程序中不起作用。似乎有什么干扰)
https://gist.github.com/pos1tron/094ac862c9d116096572
var Busboy = require('busboy'); // 0.2.9
var express = require('express'); // 4.12.3
var mongo = require('mongodb'); // 2.0.31
var Grid = require('gridfs-stream'); // 1.1.1"
var app = express();
var server = app.listen(9002);
var db = new mongo.Db('test', new mongo.Server('127.0.0.1', 27017));
var gfs;
db.open(function(err, db) {
if (err) throw err;
gfs = Grid(db, mongo);
});
app.post('/file', function(req, res) {
var busboy = new Busboy({ headers : req.headers });
var fileId = new mongo.ObjectId();
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
console.log('got file', filename, mimetype, encoding);
var writeStream = gfs.createWriteStream({
_id: fileId,
filename: filename,
mode: 'w',
content_type: mimetype,
});
file.pipe(writeStream);
}).on('finish', function() {
// show a link to the uploaded file
res.writeHead(200, {'content-type': 'text/html'});
res.end('<a href="/file/' + fileId.toString() + '">download file</a>');
});
req.pipe(busboy);
});
app.get('/', function(req, res) {
// show a file upload form
res.writeHead(200, {'content-type': 'text/html'});
res.end(
'<form action="/file" enctype="multipart/form-data" method="post">'+
'<input type="file" name="file"><br>'+
'<input type="submit" value="Upload">'+
'</form>'
);
});
app.get('/file/:id', function(req, res) {
gfs.findOne({ _id: req.params.id }, function (err, file) {
if (err) return res.status(400).send(err);
if (!file) return res.status(404).send('');
res.set('Content-Type', file.contentType);
res.set('Content-Disposition', 'attachment; filename="' + file.filename + '"');
var readstream = gfs.createReadStream({
_id: file._id
});
readstream.on("error", function(err) {
console.log("Got error while processing stream " + err.message);
res.end();
});
readstream.pipe(res);
});
});
看起来文件是通过 HTML 形式上传的,在这种情况下,您需要解码 multipart/form-data encoded data, re-assemble the parts if needed and save the file to GridFS. For parsing, you can use something like node-multiparty.
请参阅我对您在 github 上创建的问题的评论。我遇到了同样的问题,但我设法调试了这个问题。我将范围缩小到我确信问题出在一个快速中间件修改了请求的地方。我一个一个地禁用了我的中间件,直到我发现不太可能的罪魁祸首:connect-livereload
我注释掉了app.use(require('connect-livereload')());问题就消失了。
我相信它是将 livereload 脚本注入到响应(二进制图像文件)中。
我正在尝试在我的 node.js 应用程序中设置一个文件 API。我的目标是能够将文件流直接写入 gridfs,而无需最初将文件存储到磁盘。看来我的创建代码正在运行。我能够保存文件上传到 gridfs。问题是读取文件。当我尝试通过网络浏览器下载已保存的文件时 window,我看到文件内容被如下内容包裹:
------WebKitFormBoundarye38W9pfG1wiA100l
Content-Disposition: form-data; name="file"; filename="myfile.txt"
Content-Type: text/javascript
***File contents here***
------WebKitFormBoundarye38W9pfG1wiA100l--
所以我的问题是,在将文件流保存到 gridfs 之前,我需要做什么来去除文件流中的边界信息?这是我正在使用的代码:
'use strict';
var mongoose = require('mongoose');
var _ = require('lodash');
var Grid = require('gridfs-stream');
Grid.mongo = mongoose.mongo;
var gfs = new Grid(mongoose.connection.db);
// I think this works. I see the file record in fs.files
exports.create = function(req, res) {
var fileId = new mongoose.Types.ObjectId();
var writeStream = gfs.createWriteStream({
_id: fileId,
filename: req.query.name,
mode: 'w',
content_type: req.query.type,
metadata: {
uploadedBy: req.user._id,
}
});
writeStream.on('finish', function() {
return res.status(200).send({
message: fileId.toString()
});
});
req.pipe(writeStream);
};
// File data is returned, but it's wrapped with
// WebKitFormBoundary and has headers.
exports.read = function(req, res) {
gfs.findOne({ _id: req.params.id }, function (err, file) {
if (err) return res.status(400).send(err);
// With this commented out, my browser will prompt
// me to download the raw file where I can see the
// webkit boundary and request headers
//res.writeHead(200, { 'Content-Type': file.contentType });
var readstream = gfs.createReadStream({
_id: req.params.id
// I also tried this way:
//_id: file._id
});
readstream.pipe(res);
});
};
顺便说一句,我目前没有为这些路由使用任何中间件,但我愿意这样做。我只是不想让文件在发送到 gridfs 之前先进入磁盘。
编辑:
Per @fardjad,我为 multipart/form-data 解析添加了 node-multiparty 模块,它有点工作。但是当我下载上传的文件并与原始文件(如文本)进行比较时,编码存在很多差异,并且下载的文件无法打开。这是我最近的尝试。
'use strict';
var mongoose = require('mongoose');
var _ = require('lodash');
var multiparty = require('multiparty');
var Grid = require('gridfs-stream');
Grid.mongo = mongoose.mongo;
var gfs = new Grid(mongoose.connection.db);
exports.create = function(req, res) {
var form = new multiparty.Form();
var fileId = new mongoose.Types.ObjectId();
form.on('error', function(err) {
console.log('Error parsing form: ' + err.stack);
});
form.on('part', function(part) {
if (part.filename) {
var writeStream = gfs.createWriteStream({
_id: fileId,
filename: part.filename,
mode: 'w',
content_type: part.headers['content-type'],
metadata: {
uploadedBy: req.user._id,
}
})
part.pipe(writeStream);
}
});
// Close emitted after form parsed
form.on('close', function() {
return res.status(200).send({
message: fileId.toString()
});
});
// Parse req
form.parse(req);
};
exports.read = function(req, res) {
gfs.findOne({ _id: req.params.id }, function (err, file) {
if (err) return res.status(400).send(err);
res.writeHead(200, { 'Content-Type': file.contentType });
var readstream = gfs.createReadStream({
_id: req.params.id
});
readstream.pipe(res);
});
};
最终编辑:
这是我从其他开发人员那里复制并修改的一个简单实现。这对我有用:(我仍在尝试弄清楚为什么它在我原来的 express 应用程序中不起作用。似乎有什么干扰)
https://gist.github.com/pos1tron/094ac862c9d116096572
var Busboy = require('busboy'); // 0.2.9
var express = require('express'); // 4.12.3
var mongo = require('mongodb'); // 2.0.31
var Grid = require('gridfs-stream'); // 1.1.1"
var app = express();
var server = app.listen(9002);
var db = new mongo.Db('test', new mongo.Server('127.0.0.1', 27017));
var gfs;
db.open(function(err, db) {
if (err) throw err;
gfs = Grid(db, mongo);
});
app.post('/file', function(req, res) {
var busboy = new Busboy({ headers : req.headers });
var fileId = new mongo.ObjectId();
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
console.log('got file', filename, mimetype, encoding);
var writeStream = gfs.createWriteStream({
_id: fileId,
filename: filename,
mode: 'w',
content_type: mimetype,
});
file.pipe(writeStream);
}).on('finish', function() {
// show a link to the uploaded file
res.writeHead(200, {'content-type': 'text/html'});
res.end('<a href="/file/' + fileId.toString() + '">download file</a>');
});
req.pipe(busboy);
});
app.get('/', function(req, res) {
// show a file upload form
res.writeHead(200, {'content-type': 'text/html'});
res.end(
'<form action="/file" enctype="multipart/form-data" method="post">'+
'<input type="file" name="file"><br>'+
'<input type="submit" value="Upload">'+
'</form>'
);
});
app.get('/file/:id', function(req, res) {
gfs.findOne({ _id: req.params.id }, function (err, file) {
if (err) return res.status(400).send(err);
if (!file) return res.status(404).send('');
res.set('Content-Type', file.contentType);
res.set('Content-Disposition', 'attachment; filename="' + file.filename + '"');
var readstream = gfs.createReadStream({
_id: file._id
});
readstream.on("error", function(err) {
console.log("Got error while processing stream " + err.message);
res.end();
});
readstream.pipe(res);
});
});
看起来文件是通过 HTML 形式上传的,在这种情况下,您需要解码 multipart/form-data encoded data, re-assemble the parts if needed and save the file to GridFS. For parsing, you can use something like node-multiparty.
请参阅我对您在 github 上创建的问题的评论。我遇到了同样的问题,但我设法调试了这个问题。我将范围缩小到我确信问题出在一个快速中间件修改了请求的地方。我一个一个地禁用了我的中间件,直到我发现不太可能的罪魁祸首:connect-livereload
我注释掉了app.use(require('connect-livereload')());问题就消失了。 我相信它是将 livereload 脚本注入到响应(二进制图像文件)中。