在 Sails 中上傳檔案,類似於在原生 Node.js 或 Express 應用程式中上傳檔案。然而,如果您是從其他伺服器端平台(如 PHP、.NET、Python、Ruby 或 Java)轉移過來,這個過程可能會讓您感到陌生。但請別擔心:核心團隊已竭盡全力讓檔案上傳變得更容易,同時又不犧牲可擴展性或安全性。
Sails 內建強大的「body parser」Skipper,讓實作串流檔案上傳變得容易——不僅可以上傳到伺服器的檔案系統(即硬碟),還可以上傳到 Amazon S3、MongoDB 的 gridfs 或任何其他支援的檔案轉接器。
Sails 不會自動掃描上傳的檔案是否含有病毒,也不會嘗試偵測上傳的檔案是否可能已感染、損壞或異常。如果您允許使用者上傳檔案並彼此分享,您有責任保護您的使用者免受彼此的侵害。永遠假設任何進入您伺服器的請求都可能是惡意的或虛報其身分。
檔案以檔案參數的形式上傳到 HTTP 網路伺服器。就像您可能會將表單 POST 送到帶有文字參數(如「name」、「email」和「password」)的 URL 一樣,您可以將檔案作為檔案參數(如「avatar」或「newSong」)發送。
以這個簡單的範例為例
req.file('avatar').upload(function (err, uploadedFiles) {
// ...
});
檔案應在動作 (action)中上傳。以下是一個更深入的範例,示範了如何允許使用者上傳頭像圖片並將其連結到帳戶。此範例假設您已在政策 (policy) 中處理了存取控制,並且您將登入使用者的 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 伺服器網站根目錄中的路徑,或任何舊版網路伺服器可能會自動遞迴深入以執行其找到的任意程式碼檔案的目錄中。為了獲得最佳效果,請將檔案上傳到 S3 或磁碟上的安全目錄。永遠假設任何進入您伺服器的請求都可能是惡意的或虛報其身分。
在上面的範例中,我們將檔案上傳到 .tmp/uploads,但是我們如何使用自訂資料夾(例如 assets/images
)來設定它呢?我們可以透過在 upload 函數中新增選項來達成此目的,如下所示。
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 app」範本隨附的內建 Cloud SDK。(這也使得與檔案上傳一起發送的 JSON 參數可以「正常運作」,而無需額外的工作。)
從 Parasails v0.9.x 開始,捆綁的 Cloud SDK 會正確地為您處理額外的參數,因此,如果您是使用「Web app」範本產生 Sails 應用程式,您可能需要確保您使用的是最新版本的
dist/parasails.js
和dist/cloud.js
在您的專案中。
無論您在用戶端使用什麼,您都需要在後端的 Sails 動作中做一些與平常不同的事情。因為我們正在處理 multipart 上傳,所以您的請求主體中的任何文字參數必須在任何檔案之前發送。這允許 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 類型。永遠假設任何進入您伺服器的請求都可能是惡意的或虛報其身分。