Sailsでのファイルのアップロードは、Vanilla Node.jsやExpressアプリケーションでのファイルのアップロードと似ています。しかし、PHP、.NET、Python、Ruby、Javaのような他のサーバーサイドプラットフォームから来た場合、このプロセスに不慣れかもしれません。しかし、ご安心ください。コアチームは、スケーラビリティやセキュリティを犠牲にすることなく、ファイルのアップロードをより簡単にするために多大な努力を払ってきました。
Sailsには強力な「ボディパーサー」、Skipperが付属しており、サーバーのファイルシステム(つまりハードディスク)だけでなく、Amazon S3、MongoDBのgridfs、またはその他のサポートされているファイルアダプターへのストリーミングファイルアップロードを簡単に実装できます。
Sailsは、アップロードされたファイルが感染している可能性、破損している可能性、または異常であるかどうかを検出するためのウイルススキャンやその他の試みを自動的に行いません。ユーザーがファイルをアップロードして相互に共有することを許可する場合、ユーザー同士を保護する責任はあなたにあります。サーバーに送信されるすべてのリクエストは悪意がある可能性があるか、誤った表現をしている可能性があると常に想定してください。
ファイルは、HTTPウェブサーバーにファイルパラメーターとしてアップロードされます。「name」、「email」、「password」のようなテキストパラメーターをURLにフォームPOST送信するのと同じように、「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 @ http://localhost: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="http://localhost: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タイプは決して当てにすべきではありません。サーバーに送信されるすべてのリクエストは悪意がある可能性があるか、誤った表現をしている可能性があると常に想定してください。