在 Sails 中上传文件类似于在普通的 Node.js 或 Express 应用程序中上传文件。但是,如果您来自其他服务器端平台(如 PHP、.NET、Python、Ruby 或 Java),则该过程可能不熟悉。但不要担心:核心团队已经竭尽全力使文件上传变得更容易,而不会牺牲可扩展性或安全性。
Sails 带有一个强大的“主体解析器”,Skipper,它使实现流式文件上传变得容易——不仅上传到服务器的文件系统(即硬盘),还上传到 Amazon S3、MongoDB 的 gridfs 或任何其他受支持的文件适配器。
Sails 不会自动扫描文件上传的病毒,也不会尝试检测上传的文件是否可能被感染、损坏或不寻常。如果您允许用户彼此上传和共享文件,则您有责任保护用户免受彼此的伤害。始终假设进入您的服务器的任何请求都可能是恶意的或伪造自身。
文件作为文件参数上传到 HTTP web 服务器。与您可能将表单 POST 发送到带有文本参数(如“name”、“email”和“password”)的 URL 的方式相同,您将文件作为文件参数发送,例如“avatar”或“newSong”。
以这个简单的示例为例
req.file('avatar').upload(function (err, uploadedFiles) {
// ...
});
文件应在 操作 内上传。下面是一个更深入的示例,演示如何允许用户上传头像图像并将其链接到帐户。此示例假设您已经处理了策略中的访问控制,并且您将登录用户的 ID 存储在req.session.userId
中。
// api/controllers/UserController.js
//
// ...
/**
* Upload avatar for currently logged-in user
*
* (POST /user/avatar)
*/
uploadAvatar: function (req, res) {
req.file('avatar').upload({
// don't allow the total upload size to exceed ~10MB
maxBytes: 10000000
},function whenDone(err, uploadedFiles) {
if (err) {
return res.serverError(err);
}
// If no files were uploaded, respond with an error.
if (uploadedFiles.length === 0){
return res.badRequest('No file was uploaded');
}
// Get the base URL for our deployed application from our custom config
// (e.g. this might be "http://foobar.example.com:1339" or "https://example.com")
var baseUrl = sails.config.custom.baseUrl;
// Save the "fd" and the url where the avatar for a user can be accessed
User.update(req.session.userId, {
// Generate a unique URL where the avatar can be downloaded.
avatarUrl: require('util').format('%s/user/avatar/%s', baseUrl, req.session.userId),
// Grab the first file and use it's `fd` (file descriptor)
avatarFd: uploadedFiles[0].fd
})
.exec(function (err){
if (err) return res.serverError(err);
return res.ok();
});
});
},
/**
* Download avatar of the user with the specified id
*
* (GET /user/avatar/:id)
*/
avatar: function (req, res){
User.findOne(req.param('id')).exec(function (err, user){
if (err) return res.serverError(err);
if (!user) return res.notFound();
// User has no avatar image uploaded.
// (should have never have hit this endpoint and used the default image)
if (!user.avatarFd) {
return res.notFound();
}
var SkipperDisk = require('skipper-disk');
var fileAdapter = SkipperDisk(/* optional opts */);
// set the filename to the same file as the user uploaded
res.set("Content-disposition", "attachment; filename='" + file.name + "'");
// Stream the file down
fileAdapter.read(user.avatarFd)
.on('error', function (err){
return res.serverError(err);
})
.pipe(res);
});
}
//
// ...
使用默认的receiver
时,文件上传到myApp/.tmp/uploads/
目录。这可以使用dirname
选项覆盖。请注意,您需要在调用.upload()
函数和调用 skipper-disk 适配器时都指定此选项(以便您上传到同一个地方并从同一个地方下载)。
任何接收不受信任的文件上传并将它们存储在磁盘上的 Node.js 应用程序(或其他服务器端应用程序)永远不应该将这些文件上传到 Java 服务器 Web 根目录或任何传统 Web 服务器可能会自动递归深入以执行它找到的任意代码文件。为了获得最佳效果,将文件上传到 S3 或磁盘上的安全目录。始终假设进入您的服务器的任何请求都可能是恶意的或伪造自身。
在上面的示例中,我们将文件上传到 .tmp/uploads,但是如何使用自定义文件夹(例如assets/images
)配置它呢?我们可以通过在上传函数中添加选项来实现,如下所示。
req.file('avatar').upload({
dirname: require('path').resolve(sails.config.appPath, 'assets/images')
},function (err, uploadedFiles) {
if (err) return res.serverError(err);
return res.json({
message: uploadedFiles.length + ' file(s) uploaded successfully!'
});
});
如果您需要与文件上传一起发送文本参数,最简单的方法是将它们包含在 URL 中。
如果您必须在请求正文中发送文本参数,最简单的方法是使用“Web 应用程序”模板附带的内置 Cloud SDK。(这还使得与文件上传一起发送的 JSON 参数在没有额外工作的情况下“正常工作”。)
从 Parasails v0.9.x 开始,捆绑的 Cloud SDK 会为您正确处理其他参数,因此,如果您使用“Web 应用程序”模板生成了 Sails 应用程序,您可能需要确保您使用的是最新版本的
dist/parasails.js
和dist/cloud.js
在您的项目中。
无论您在客户端使用什么,您都需要在后端的 Sails 操作中以与平常略有不同的方式进行操作。由于我们正在处理多部分上传,因此您请求正文中的任何文本参数都必须在任何文件之前发送。这使 Sails 能够在文件仍在上传时运行您的操作代码,而不是必须等待它们完成(避免Express 基于 Node.js 应用程序中著名的 DDoS 漏洞)。请参阅Skipper 文档以获取有关幕后工作原理的更多高级信息。
api
首先,我们需要生成一个用于服务/存储文件的新的api
。使用 sails 命令行工具执行此操作。
$ sails generate api file
debug: Generated a new controller `file` at api/controllers/FileController.js!
debug: Generated a new model `File` at api/models/File.js!
info: REST API generated @ https://127.0.0.1:1337/file
info: and will be available the next time you run `sails lift`.
让我们创建一个index
操作来启动文件上传,以及一个upload
操作来接收文件。
// myApp/api/controllers/FileController.js
module.exports = {
index: function (req,res){
res.writeHead(200, {'content-type': 'text/html'});
res.end(
'<form action="https://127.0.0.1:1337/file/upload" enctype="multipart/form-data" method="post">'+
'<input type="text" name="title"><br>'+
'<input type="file" name="avatar" multiple="multiple"><br>'+
'<input type="submit" value="Upload">'+
'</form>'
)
},
upload: function (req, res) {
req.file('avatar').upload(function (err, files) {
if (err)
return res.serverError(err);
return res.json({
message: files.length + ' file(s) uploaded successfully!',
files: files
});
});
}
};
虽然将不受信任的 JavaScript 作为
<img src="…">
在现代浏览器中不是 XSS 漏洞,但文件上传的请求标头中的 MIME 类型永远不应该依赖。始终假设进入您的服务器的任何请求都可能是恶意的或伪造自身。