ユーザーが画像ファイルをサーバーにアップロードできるSails-jsベースのアプリケーションを作成しています。ファイルをディスクに保存する前に、ファイルが本当に画像ファイルかどうかを確認したいと思います。
スキッパー ディスク アダプターと格闘した後、アップロードを完了する前にファイルのプロパティを確認できなかったようです。ファイルの種類を確認して、または同様の応答を試みた場合、アップロードを終了させてres.json("error, wrong file type")
から応答しない限り、クライアントは応答を受け取りませんでした。これは、アップロード後にすべての検証を行う必要があることも意味します。ミドルウェアを介してエンドポイントを保護する Sails ポリシーがあるため、これは特に厄介です。未完成のアップロードは、コントローラーの前に実行する必要がある認証ミドルウェアでさえも何とかブロックします。
以下は、ファイルをディスクに保存し、検証エラーが発生した場合は削除する私の最善の試みです。非常に不満です。また、認証されていないユーザーがファイルをアップロードしようとすると、応答しません。
create: function (req, res) {
var valid = true;
if (!req.body.headline || !req.body.content) {
valid = false;
}
var settings = {
maxBytes: 10000000,
dirname: path.join(sails.config.appPath, '/assets/images')
};
req.file('image').upload(settings, function whenDone(err, uploadedFiles) {
if (err) {
return res.negotiate(err);
}
if (uploadedFiles[0]) {
var upload = req.file('image')._files[0].stream,
headers = upload.headers,
allowedTypes = ['image/jpeg', 'image/png'];
if (_.indexOf(allowedTypes, headers['content-type']) === -1 || !valid) {
//file type is not supported, abort and delete file
/** TODO: Ideally we would check the file type and other validations before upload and saving. However,
* skipper didn't seem to support any obvious ways to abort requests at the moment of writing:
* https://github.com/balderdashy/skipper/issues/80
* https://stackoverflow.com/questions/31061719/sails-js-with-skipper-check-if-file-input-were-empty-before-starting-upload
*
* Therefore returning from this request before finishing upload results hanging response. Investigate
* alternatives when more time.
*
* NOTE: If unauthenticated user with file upload tries to use this endpoint, they will not get response, since
* authentication middleware rejects request but skipper prevents server from responding!
*/
var fileAdapter = SkipperDisk();
return fileAdapter.rm(uploadedFiles[0].fd, function () {
res.status(400);
return res.json({message: 'Wrong fileformat or missing attributes. Please provide .png or .jpg file and ' +
'ensure you have content and headline fields defined.'});
});
}
}
if (valid) {
Announcement.create({
headline: req.body.headline,
content: req.body.content,
author: req.session.user})
.then(function(announcement) {
if (uploadedFiles[0]) {
announcement.imagePath = uploadedFiles[0].fd;
announcement.imageUrl = '/announcements/' + announcement.id + '/image';
} else {
announcement.imagePath = null;
announcement.imageUrl = null;
}
announcement.save(function(err, saved) {
return res.json(saved);
});
});
} else {
res.status(400);
return res.json({message: 'Missing attributes content and/or headline.'});
}
});
}
Skipper のディスク アダプターに不満を感じた後、そのドキュメントを参照し、独自のレシーバーの作成に関するドキュメントを見つけました。その情報とStackoverflow の同様の問題を利用して、次のコードを作成しました。
create: function (req, res) {
var allowedTypes = ['image/jpeg', 'image/png'];
//output stream to disk
var output = require('fs').createWriteStream('./storedImage.png');
//custom receiver for Skipper
var receiver = new stream.Writable({objectMode: true});
receiver._write = function(file, enc, done) {
file.pipe(output);
output.once('finish', function () {
console.log("file transfer finished");
receiver.end();
return done();
});
file.once('readable', function() {
//tiedoston luku aloitetaan.
var headers = req.file('image')._files[0].stream.headers;
console.log("reading file...");
req.validate({
headline: 'string',
content: 'string'
});
if (_.indexOf(allowedTypes, headers['content-type']) === -1) {
console.log("forbidden img type!");
file.end();
}
});
file.once('data', function(d) {
console.log(d)
});
file.once('end', function() {
console.log("input end");
output.end();
});
output.on('error', function (err) {
console.log("error in output stream ", err);
return done({
incoming: file,
outgoing: output,
code: 'E_WRITE',
stack: typeof err === 'object' ? err.stack : new Error(err),
name: typeof err === 'object' ? err.name : err,
message: typeof err === 'object' ? err.message : err
});
});
};
req.file('image').upload(receiver, function(err, files) {
if (err) {
return res.json("There was a problem :(");
}
console.log(files);
return res.json({success: files });
});
このようにして、ストリームをもう少し制御できるようになり、リクエストからストリームを取得してからファイルストリームにパイプするという基本的な考え方を理解したと思います。読み取り可能なイベントで、ファイルのタイプを確認しようとします。ただし、ストリームの中止は依然として問題です。試行錯誤の末end()
、ファイル ストリームと出力ストリームの両方に対して関数を呼び出すことができました。ストリームを正しく理解していれば、ストリームを閉じる必要があります。req.file('image).upload
レシーバーが間違ったファイル タイプを検出した直後に、関数から正しい見た目の戻り値を取得します。しかし、私はまだ応答を返すことができます! 本当に大きなファイルで試した後、次のようになります
file.on('data', function(d) {
console.log(d)
});
新しいチャンクをログに記録し続けます。これは、ファイル ストリームが期待どおりに閉じられなかったことを意味します。
最後に私の最後の質問は、Sails.js/Skipper で着信 http-request ストリームを適切に中止するにはどうすればよいですか?